Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
9dbca64417
commit
b8d021cb60
|
|
@ -10,74 +10,6 @@ doc/api/graphql/reference/gitlab_schema.graphql
|
|||
*.scss
|
||||
*.md
|
||||
|
||||
## lovely-lovelace
|
||||
app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
|
||||
app/assets/javascripts/behaviors/markdown/paste_markdown_table.js
|
||||
app/assets/javascripts/boards/components/sidebar/remove_issue.vue
|
||||
app/assets/javascripts/diffs/components/diff_row.vue
|
||||
app/assets/javascripts/diffs/store/getters.js
|
||||
app/assets/javascripts/dropzone_input.js
|
||||
app/assets/javascripts/feature_flags/components/strategy.vue
|
||||
app/assets/javascripts/ide/lib/create_diff.js
|
||||
app/assets/javascripts/ide/stores/modules/pipelines/getters.js
|
||||
app/assets/javascripts/members/components/table/members_table.vue
|
||||
app/assets/javascripts/members/store/utils.js
|
||||
app/assets/javascripts/monitoring/stores/actions.js
|
||||
app/assets/javascripts/monitoring/stores/getters.js
|
||||
app/assets/javascripts/packages/list/utils.js
|
||||
app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list.vue
|
||||
app/assets/javascripts/pages/users/user_tabs.js
|
||||
app/assets/javascripts/projects/settings/access_dropdown.js
|
||||
|
||||
## stoic-swirles
|
||||
app/assets/javascripts/repository/log_tree.js
|
||||
app/assets/javascripts/repository/utils/dom.js
|
||||
app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue
|
||||
app/assets/javascripts/user_lists/store/utils.js
|
||||
app/assets/javascripts/vue_shared/components/alert_details_table.vue
|
||||
app/assets/javascripts/vue_shared/components/upload_dropzone/upload_dropzone.vue
|
||||
app/assets/javascripts/vue_shared/constants.js
|
||||
ee/app/assets/javascripts/analytics/cycle_analytics/utils.js
|
||||
ee/app/assets/javascripts/analytics/repository_analytics/components/select_projects_dropdown.vue
|
||||
ee/app/assets/javascripts/boards/stores/getters.js
|
||||
ee/app/assets/javascripts/dependencies/store/modules/list/getters.js
|
||||
ee/app/assets/javascripts/epic/store/getters.js
|
||||
ee/app/assets/javascripts/insights/components/insights_page.vue
|
||||
ee/app/assets/javascripts/pages/trial_registrations/new/username_suggester.js
|
||||
ee/app/assets/javascripts/related_items_tree/store/mutations.js
|
||||
ee/app/assets/javascripts/security_dashboard/components/project_vulnerabilities.vue
|
||||
ee/spec/frontend/analytics/shared/components/groups_dropdown_filter_spec.js
|
||||
ee/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js
|
||||
ee/spec/frontend/approvals/components/approvers_list_spec.js
|
||||
ee/spec/frontend/approvals/components/rule_controls_spec.js
|
||||
ee/spec/frontend/audit_events/components/audit_events_filter_spec.js
|
||||
ee/spec/frontend/dependencies/components/dependencies_table_spec.js
|
||||
ee/spec/frontend/geo_node_form/components/geo_node_form_capacities_spec.js
|
||||
ee/spec/frontend/security_configuration/dast_profiles/graphql/cache_utils_spec.js
|
||||
ee/spec/frontend/security_configuration/dast_site_profiles_form/components/dast_site_profile_form_spec.js
|
||||
ee/spec/frontend/security_configuration/dast_site_validation/components/dast_site_validation_modal_spec.js
|
||||
ee/spec/frontend/security_dashboard/components/vulnerability_list_spec.js
|
||||
ee/spec/frontend/sidebar/components/status/status_spec.js
|
||||
ee/spec/frontend/storage_counter/components/projects_table_spec.js
|
||||
ee/spec/frontend/vulnerabilities/footer_spec.js
|
||||
spec/frontend/blob/balsamiq/balsamiq_viewer_spec.js
|
||||
|
||||
## objective-swirles
|
||||
spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js
|
||||
spec/frontend/clusters/stores/clusters_store_spec.js
|
||||
spec/frontend/diffs/components/diff_file_header_spec.js
|
||||
spec/frontend/diffs/components/hidden_files_warning_spec.js
|
||||
spec/frontend/environments/environment_monitoring_spec.js
|
||||
spec/frontend/import_entities/import_groups/graphql/services/status_poller_spec.js
|
||||
spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js
|
||||
spec/frontend/notes/components/discussion_filter_spec.js
|
||||
spec/frontend/pipeline_editor/components/lint/ci_lint_results_spec.js
|
||||
spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js
|
||||
spec/frontend/snippets/components/edit_spec.js
|
||||
spec/frontend/user_lists/store/show/mutations_spec.js
|
||||
spec/frontend/vue_shared/components/stacked_progress_bar_spec.js
|
||||
spec/frontend_integration/ide/helpers/ide_helper.js
|
||||
|
||||
## boring-bohr
|
||||
jest.config.base.js
|
||||
jest.config.js
|
||||
|
|
|
|||
|
|
@ -2,6 +2,13 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 13.7.1 (2020-12-23)
|
||||
|
||||
### Fixed (1 change)
|
||||
|
||||
- Fix project transfer corrupting shared runners state. !47316
|
||||
|
||||
|
||||
## 13.7.0 (2020-12-22)
|
||||
|
||||
### Security (1 change)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import {
|
|||
} from '~/vue_shared/components/paginated_table_with_search_and_tabs/constants';
|
||||
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import { convertToSnakeCase } from '~/lib/utils/text_utility';
|
||||
import getAlerts from '../graphql/queries/get_alerts.query.graphql';
|
||||
import getAlerts from '~/graphql_shared/queries/get_alerts.query.graphql';
|
||||
import getAlertsCountByStatus from '../graphql/queries/get_count_by_status.query.graphql';
|
||||
import {
|
||||
ALERTS_STATUS_TABS,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
|
|||
import { s__ } from '~/locale';
|
||||
import Tracking from '~/tracking';
|
||||
import { trackAlertStatusUpdateOptions } from '../constants';
|
||||
import updateAlertStatusMutation from '../graphql/mutations/update_alert_status.mutation.graphql';
|
||||
import updateAlertStatusMutation from '~/graphql_shared/mutations/update_alert_status.mutation.graphql';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#import "./list_item.fragment.graphql"
|
||||
#import "./alert_note.fragment.graphql"
|
||||
#import "~/graphql_shared/fragments/alert.fragment.graphql"
|
||||
#import "~/graphql_shared/fragments/alert_note.fragment.graphql"
|
||||
|
||||
fragment AlertDetailItem on AlertManagementAlert {
|
||||
...AlertListItem
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#import "../fragments/alert_note.fragment.graphql"
|
||||
#import "~/graphql_shared/fragments/alert_note.fragment.graphql"
|
||||
|
||||
mutation alertSetAssignees($projectPath: ID!, $assigneeUsernames: [String!]!, $iid: String!) {
|
||||
alertSetAssignees(
|
||||
|
|
|
|||
|
|
@ -243,7 +243,9 @@ export default {
|
|||
});
|
||||
},
|
||||
editIntegration({ id }) {
|
||||
const currentIntegration = this.integrations.list.find(integration => integration.id === id);
|
||||
const currentIntegration = this.integrations.list.find(
|
||||
(integration) => integration.id === id,
|
||||
);
|
||||
this.$apollo.mutate({
|
||||
mutation: updateCurrentIntergrationMutation,
|
||||
variables: {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const maxColumnWidth = (rows, columnIndex) => Math.max(...rows.map(row => row[columnIndex].length));
|
||||
const maxColumnWidth = (rows, columnIndex) =>
|
||||
Math.max(...rows.map((row) => row[columnIndex].length));
|
||||
|
||||
export default class PasteMarkdownTable {
|
||||
constructor(clipboardData) {
|
||||
|
|
@ -16,7 +17,7 @@ export default class PasteMarkdownTable {
|
|||
this.calculateColumnWidths();
|
||||
|
||||
const markdownRows = this.rows.map(
|
||||
row =>
|
||||
(row) =>
|
||||
// | Name | Title | Email Address |
|
||||
// |--------------|-------|----------------|
|
||||
// | Jane Atler | CEO | jane@acme.com |
|
||||
|
|
@ -66,7 +67,7 @@ export default class PasteMarkdownTable {
|
|||
return false;
|
||||
}
|
||||
|
||||
this.rows = splitRows.map(row => row.split('\t'));
|
||||
this.rows = splitRows.map((row) => row.split('\t'));
|
||||
this.normalizeRows();
|
||||
|
||||
// Check that the max number of columns in the HTML matches the number of
|
||||
|
|
@ -81,10 +82,10 @@ export default class PasteMarkdownTable {
|
|||
|
||||
// Ensure each row has the same number of columns
|
||||
normalizeRows() {
|
||||
const rowLengths = this.rows.map(row => row.length);
|
||||
const rowLengths = this.rows.map((row) => row.length);
|
||||
const maxLength = Math.max(...rowLengths);
|
||||
|
||||
this.rows.forEach(row => {
|
||||
this.rows.forEach((row) => {
|
||||
while (row.length < maxLength) {
|
||||
row.push('');
|
||||
}
|
||||
|
|
@ -101,7 +102,7 @@ export default class PasteMarkdownTable {
|
|||
const textColumnCount = this.rows[0].length;
|
||||
let htmlColumnCount = 0;
|
||||
|
||||
this.doc.querySelectorAll('table tr').forEach(row => {
|
||||
this.doc.querySelectorAll('table tr').forEach((row) => {
|
||||
htmlColumnCount = Math.max(row.cells.length, htmlColumnCount);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -42,13 +42,13 @@ export default {
|
|||
axios.patch(this.updateUrl, data).catch(() => {
|
||||
Flash(__('Failed to remove issue from board, please try again.'));
|
||||
|
||||
lists.forEach(list => {
|
||||
lists.forEach((list) => {
|
||||
list.addIssue(issue);
|
||||
});
|
||||
});
|
||||
|
||||
// Remove from the frontend store
|
||||
lists.forEach(list => {
|
||||
lists.forEach((list) => {
|
||||
list.removeIssue(issue);
|
||||
});
|
||||
|
||||
|
|
@ -58,9 +58,11 @@ export default {
|
|||
* Build the default patch request.
|
||||
*/
|
||||
buildPatchRequest(issue, lists) {
|
||||
const listLabelIds = lists.map(list => list.label.id);
|
||||
const listLabelIds = lists.map((list) => list.label.id);
|
||||
|
||||
const labelIds = issue.labels.map(label => label.id).filter(id => !listLabelIds.includes(id));
|
||||
const labelIds = issue.labels
|
||||
.map((label) => label.id)
|
||||
.filter((id) => !listLabelIds.includes(id));
|
||||
|
||||
return {
|
||||
label_ids: labelIds,
|
||||
|
|
|
|||
|
|
@ -115,7 +115,9 @@ export default {
|
|||
const table = line.closest('.diff-table');
|
||||
|
||||
table.classList.remove('left-side-selected', 'right-side-selected');
|
||||
const [lineClass] = ['left-side', 'right-side'].filter(name => line.classList.contains(name));
|
||||
const [lineClass] = ['left-side', 'right-side'].filter((name) =>
|
||||
line.classList.contains(name),
|
||||
);
|
||||
|
||||
if (lineClass) {
|
||||
table.classList.add(`${lineClass}-selected`);
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@ import {
|
|||
|
||||
export * from './getters_versions_dropdowns';
|
||||
|
||||
export const isParallelView = state => state.diffViewType === PARALLEL_DIFF_VIEW_TYPE;
|
||||
export const isParallelView = (state) => state.diffViewType === PARALLEL_DIFF_VIEW_TYPE;
|
||||
|
||||
export const isInlineView = state => state.diffViewType === INLINE_DIFF_VIEW_TYPE;
|
||||
export const isInlineView = (state) => state.diffViewType === INLINE_DIFF_VIEW_TYPE;
|
||||
|
||||
export const whichCollapsedTypes = state => {
|
||||
const automatic = state.diffFiles.some(file => file.viewer?.automaticallyCollapsed);
|
||||
const manual = state.diffFiles.some(file => file.viewer?.manuallyCollapsed);
|
||||
export const whichCollapsedTypes = (state) => {
|
||||
const automatic = state.diffFiles.some((file) => file.viewer?.automaticallyCollapsed);
|
||||
const manual = state.diffFiles.some((file) => file.viewer?.manuallyCollapsed);
|
||||
|
||||
return {
|
||||
any: automatic || manual,
|
||||
|
|
@ -24,18 +24,18 @@ export const whichCollapsedTypes = state => {
|
|||
};
|
||||
};
|
||||
|
||||
export const commitId = state => (state.commit && state.commit.id ? state.commit.id : null);
|
||||
export const commitId = (state) => (state.commit && state.commit.id ? state.commit.id : null);
|
||||
|
||||
/**
|
||||
* Checks if the diff has all discussions expanded
|
||||
* @param {Object} diff
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export const diffHasAllExpandedDiscussions = (state, getters) => diff => {
|
||||
export const diffHasAllExpandedDiscussions = (state, getters) => (diff) => {
|
||||
const discussions = getters.getDiffFileDiscussions(diff);
|
||||
|
||||
return (
|
||||
(discussions && discussions.length && discussions.every(discussion => discussion.expanded)) ||
|
||||
(discussions && discussions.length && discussions.every((discussion) => discussion.expanded)) ||
|
||||
false
|
||||
);
|
||||
};
|
||||
|
|
@ -45,11 +45,13 @@ export const diffHasAllExpandedDiscussions = (state, getters) => diff => {
|
|||
* @param {Object} diff
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export const diffHasAllCollapsedDiscussions = (state, getters) => diff => {
|
||||
export const diffHasAllCollapsedDiscussions = (state, getters) => (diff) => {
|
||||
const discussions = getters.getDiffFileDiscussions(diff);
|
||||
|
||||
return (
|
||||
(discussions && discussions.length && discussions.every(discussion => !discussion.expanded)) ||
|
||||
(discussions &&
|
||||
discussions.length &&
|
||||
discussions.every((discussion) => !discussion.expanded)) ||
|
||||
false
|
||||
);
|
||||
};
|
||||
|
|
@ -59,9 +61,9 @@ export const diffHasAllCollapsedDiscussions = (state, getters) => diff => {
|
|||
* @param {Object} diff
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export const diffHasExpandedDiscussions = () => diff => {
|
||||
return diff[INLINE_DIFF_LINES_KEY].filter(l => l.discussions.length >= 1).some(
|
||||
l => l.discussionsExpanded,
|
||||
export const diffHasExpandedDiscussions = () => (diff) => {
|
||||
return diff[INLINE_DIFF_LINES_KEY].filter((l) => l.discussions.length >= 1).some(
|
||||
(l) => l.discussionsExpanded,
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -70,8 +72,8 @@ export const diffHasExpandedDiscussions = () => diff => {
|
|||
* @param {Boolean} diff
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export const diffHasDiscussions = () => diff => {
|
||||
return diff[INLINE_DIFF_LINES_KEY].some(l => l.discussions.length >= 1);
|
||||
export const diffHasDiscussions = () => (diff) => {
|
||||
return diff[INLINE_DIFF_LINES_KEY].some((l) => l.discussions.length >= 1);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -79,22 +81,22 @@ export const diffHasDiscussions = () => diff => {
|
|||
* @param {Object} diff
|
||||
* @returns {Array}
|
||||
*/
|
||||
export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) => diff =>
|
||||
export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) => (diff) =>
|
||||
rootGetters.discussions.filter(
|
||||
discussion => discussion.diff_discussion && discussion.diff_file.file_hash === diff.file_hash,
|
||||
(discussion) => discussion.diff_discussion && discussion.diff_file.file_hash === diff.file_hash,
|
||||
) || [];
|
||||
|
||||
export const getDiffFileByHash = state => fileHash =>
|
||||
state.diffFiles.find(file => file.file_hash === fileHash);
|
||||
export const getDiffFileByHash = (state) => (fileHash) =>
|
||||
state.diffFiles.find((file) => file.file_hash === fileHash);
|
||||
|
||||
export const flatBlobsList = state =>
|
||||
Object.values(state.treeEntries).filter(f => f.type === 'blob');
|
||||
export const flatBlobsList = (state) =>
|
||||
Object.values(state.treeEntries).filter((f) => f.type === 'blob');
|
||||
|
||||
export const allBlobs = (state, getters) =>
|
||||
getters.flatBlobsList.reduce((acc, file) => {
|
||||
const { parentPath } = file;
|
||||
|
||||
if (parentPath && !acc.some(f => f.path === parentPath)) {
|
||||
if (parentPath && !acc.some((f) => f.path === parentPath)) {
|
||||
acc.push({
|
||||
path: parentPath,
|
||||
isHeader: true,
|
||||
|
|
@ -102,13 +104,13 @@ export const allBlobs = (state, getters) =>
|
|||
});
|
||||
}
|
||||
|
||||
acc.find(f => f.path === parentPath).tree.push(file);
|
||||
acc.find((f) => f.path === parentPath).tree.push(file);
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
export const getCommentFormForDiffFile = state => fileHash =>
|
||||
state.commentForms.find(form => form.fileHash === fileHash);
|
||||
export const getCommentFormForDiffFile = (state) => (fileHash) =>
|
||||
state.commentForms.find((form) => form.fileHash === fileHash);
|
||||
|
||||
/**
|
||||
* Returns the test coverage hits for a specific line of a given file
|
||||
|
|
@ -116,7 +118,7 @@ export const getCommentFormForDiffFile = state => fileHash =>
|
|||
* @param {number} line
|
||||
* @returns {number}
|
||||
*/
|
||||
export const fileLineCoverage = state => (file, line) => {
|
||||
export const fileLineCoverage = (state) => (file, line) => {
|
||||
if (!state.coverageFiles.files) return {};
|
||||
const fileCoverage = state.coverageFiles.files[file];
|
||||
if (!fileCoverage) return {};
|
||||
|
|
@ -137,13 +139,13 @@ export const fileLineCoverage = state => (file, line) => {
|
|||
* Returns index of a currently selected diff in diffFiles
|
||||
* @returns {number}
|
||||
*/
|
||||
export const currentDiffIndex = state =>
|
||||
export const currentDiffIndex = (state) =>
|
||||
Math.max(
|
||||
0,
|
||||
state.diffFiles.findIndex(diff => diff.file_hash === state.currentDiffFileId),
|
||||
state.diffFiles.findIndex((diff) => diff.file_hash === state.currentDiffFileId),
|
||||
);
|
||||
|
||||
export const diffLines = state => (file, unifiedDiffComponents) => {
|
||||
export const diffLines = (state) => (file, unifiedDiffComponents) => {
|
||||
if (!unifiedDiffComponents && state.diffViewType === INLINE_DIFF_VIEW_TYPE) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -155,5 +157,5 @@ export const diffLines = state => (file, unifiedDiffComponents) => {
|
|||
};
|
||||
|
||||
export function fileReviews(state) {
|
||||
return state.diffFiles.map(file => isFileReviewed(state.mrReviews, file));
|
||||
return state.diffFiles.map((file) => isFileReviewed(state.mrReviews, file));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export default function dropzoneInput(form, config = { parallelUploads: 2 }) {
|
|||
let uploadFile;
|
||||
|
||||
formTextarea.wrap('<div class="div-dropzone"></div>');
|
||||
formTextarea.on('paste', event => handlePaste(event));
|
||||
formTextarea.on('paste', (event) => handlePaste(event));
|
||||
|
||||
// Add dropzone area to the form.
|
||||
const $mdArea = formTextarea.closest('.md-area');
|
||||
|
|
@ -139,7 +139,7 @@ export default function dropzoneInput(form, config = { parallelUploads: 2 }) {
|
|||
|
||||
// removeAllFiles(true) stops uploading files (if any)
|
||||
// and remove them from dropzone files queue.
|
||||
$cancelButton.on('click', e => {
|
||||
$cancelButton.on('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
Dropzone.forElement($formDropzone.get(0)).removeAllFiles(true);
|
||||
|
|
@ -149,7 +149,7 @@ export default function dropzoneInput(form, config = { parallelUploads: 2 }) {
|
|||
// clear dropzone files queue, change status of failed files to undefined,
|
||||
// and add that files to the dropzone files queue again.
|
||||
// addFile() adds file to dropzone files queue and upload it.
|
||||
$retryLink.on('click', e => {
|
||||
$retryLink.on('click', (e) => {
|
||||
const dropzoneInstance = Dropzone.forElement(
|
||||
e.target.closest('.js-main-target-form').querySelector('.div-dropzone'),
|
||||
);
|
||||
|
|
@ -161,7 +161,7 @@ export default function dropzoneInput(form, config = { parallelUploads: 2 }) {
|
|||
// uploading of files that are being uploaded at the moment.
|
||||
dropzoneInstance.removeAllFiles(true);
|
||||
|
||||
failedFiles.map(failedFile => {
|
||||
failedFiles.map((failedFile) => {
|
||||
const file = failedFile;
|
||||
|
||||
if (file.status === Dropzone.ERROR) {
|
||||
|
|
@ -173,7 +173,7 @@ export default function dropzoneInput(form, config = { parallelUploads: 2 }) {
|
|||
});
|
||||
});
|
||||
// eslint-disable-next-line consistent-return
|
||||
handlePaste = event => {
|
||||
handlePaste = (event) => {
|
||||
const pasteEvent = event.originalEvent;
|
||||
const { clipboardData } = pasteEvent;
|
||||
if (clipboardData && clipboardData.items) {
|
||||
|
|
@ -198,7 +198,7 @@ export default function dropzoneInput(form, config = { parallelUploads: 2 }) {
|
|||
}
|
||||
};
|
||||
|
||||
isImage = data => {
|
||||
isImage = (data) => {
|
||||
let i = 0;
|
||||
while (i < data.clipboardData.items.length) {
|
||||
const item = data.clipboardData.items[i];
|
||||
|
|
@ -228,7 +228,7 @@ export default function dropzoneInput(form, config = { parallelUploads: 2 }) {
|
|||
return formTextarea.trigger('input');
|
||||
};
|
||||
|
||||
addFileToForm = path => {
|
||||
addFileToForm = (path) => {
|
||||
$(form).append(`<input type="hidden" name="files[]" value="${escape(path)}">`);
|
||||
};
|
||||
|
||||
|
|
@ -236,7 +236,7 @@ export default function dropzoneInput(form, config = { parallelUploads: 2 }) {
|
|||
|
||||
const closeSpinner = () => $uploadingProgressContainer.addClass('hide');
|
||||
|
||||
const showError = message => {
|
||||
const showError = (message) => {
|
||||
$uploadingErrorContainer.removeClass('hide');
|
||||
$uploadingErrorMessage.html(message);
|
||||
};
|
||||
|
|
@ -269,15 +269,16 @@ export default function dropzoneInput(form, config = { parallelUploads: 2 }) {
|
|||
insertToTextArea(filename, md);
|
||||
closeSpinner();
|
||||
})
|
||||
.catch(e => {
|
||||
.catch((e) => {
|
||||
showError(e.response.data.message);
|
||||
closeSpinner();
|
||||
});
|
||||
};
|
||||
|
||||
updateAttachingMessage = (files, messageContainer) => {
|
||||
const filesCount = files.filter(file => file.status === 'uploading' || file.status === 'queued')
|
||||
.length;
|
||||
const filesCount = files.filter(
|
||||
(file) => file.status === 'uploading' || file.status === 'queued',
|
||||
).length;
|
||||
const attachingMessage = n__('Attaching a file', 'Attaching %d files', filesCount);
|
||||
|
||||
messageContainer.text(`${attachingMessage} -`);
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ export default {
|
|||
);
|
||||
},
|
||||
filteredEnvironments() {
|
||||
return this.environments.filter(e => !e.shouldBeDestroyed);
|
||||
return this.environments.filter((e) => !e.shouldBeDestroyed);
|
||||
},
|
||||
isPercentUserRollout() {
|
||||
return this.formStrategy.name === ROLLOUT_STRATEGY_PERCENT_ROLLOUT;
|
||||
|
|
@ -91,7 +91,9 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
addEnvironment(environment) {
|
||||
const allEnvironmentsScope = this.environments.find(scope => scope.environmentScope === '*');
|
||||
const allEnvironmentsScope = this.environments.find(
|
||||
(scope) => scope.environmentScope === '*',
|
||||
);
|
||||
if (allEnvironmentsScope) {
|
||||
allEnvironmentsScope.shouldBeDestroyed = true;
|
||||
}
|
||||
|
|
@ -113,7 +115,7 @@ export default {
|
|||
if (isNumber(environment.id)) {
|
||||
Vue.set(environment, 'shouldBeDestroyed', true);
|
||||
} else {
|
||||
this.environments = this.environments.filter(e => e !== environment);
|
||||
this.environments = this.environments.filter((e) => e !== environment);
|
||||
}
|
||||
if (this.filteredEnvironments.length === 0) {
|
||||
this.environments.push({ environmentScope: '*' });
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#import "../fragments/alert_note.fragment.graphql"
|
||||
#import "~/graphql_shared/fragments/alert_note.fragment.graphql"
|
||||
|
||||
mutation updateAlertStatus($projectPath: ID!, $status: AlertManagementStatus!, $iid: String!) {
|
||||
updateAlertStatus(input: { iid: $iid, status: $status, projectPath: $projectPath }) {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#import "../fragments/list_item.fragment.graphql"
|
||||
#import "~/graphql_shared/fragments/alert.fragment.graphql"
|
||||
|
||||
query getAlerts(
|
||||
$projectPath: ID!
|
||||
|
|
@ -32,8 +32,8 @@ const filesWithChanges = ({ stagedFiles = [], changedFiles = [], entries = {} })
|
|||
// We need to clean "move" actions, because we can only support 100% similarity moves at the moment.
|
||||
// This is because the previous file's content might not be loaded.
|
||||
Object.values(changes)
|
||||
.filter(change => change.action === commitActionTypes.move)
|
||||
.forEach(change => {
|
||||
.filter((change) => change.action === commitActionTypes.move)
|
||||
.forEach((change) => {
|
||||
const prev = changes[change.file.prevPath];
|
||||
|
||||
if (!prev) {
|
||||
|
|
@ -51,14 +51,14 @@ const filesWithChanges = ({ stagedFiles = [], changedFiles = [], entries = {} })
|
|||
|
||||
// Next, we need to add deleted directories by looking at the parents
|
||||
Object.values(changes)
|
||||
.filter(change => change.action === commitActionTypes.delete && change.file.parentPath)
|
||||
.filter((change) => change.action === commitActionTypes.delete && change.file.parentPath)
|
||||
.forEach(({ file }) => {
|
||||
// Do nothing if we've already visited this directory.
|
||||
if (changes[file.parentPath]) {
|
||||
return;
|
||||
}
|
||||
|
||||
getDeletedParents(entries, file).forEach(parent => {
|
||||
getDeletedParents(entries, file).forEach((parent) => {
|
||||
changes[parent.path] = { action: commitActionTypes.delete, file: parent };
|
||||
});
|
||||
});
|
||||
|
|
@ -66,13 +66,15 @@ const filesWithChanges = ({ stagedFiles = [], changedFiles = [], entries = {} })
|
|||
return Object.values(changes);
|
||||
};
|
||||
|
||||
const createDiff = state => {
|
||||
const createDiff = (state) => {
|
||||
const changes = filesWithChanges(state);
|
||||
|
||||
const toDelete = changes.filter(x => x.action === commitActionTypes.delete).map(x => x.file.path);
|
||||
const toDelete = changes
|
||||
.filter((x) => x.action === commitActionTypes.delete)
|
||||
.map((x) => x.file.path);
|
||||
|
||||
const patch = changes
|
||||
.filter(x => x.action !== commitActionTypes.delete)
|
||||
.filter((x) => x.action !== commitActionTypes.delete)
|
||||
.map(({ file, action }) => createFileDiff(file, action))
|
||||
.join('');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,23 @@
|
|||
import { states } from './constants';
|
||||
|
||||
export const hasLatestPipeline = state => !state.isLoadingPipeline && Boolean(state.latestPipeline);
|
||||
export const hasLatestPipeline = (state) =>
|
||||
!state.isLoadingPipeline && Boolean(state.latestPipeline);
|
||||
|
||||
export const pipelineFailed = state =>
|
||||
export const pipelineFailed = (state) =>
|
||||
state.latestPipeline && state.latestPipeline.details.status.text === states.failed;
|
||||
|
||||
export const failedStages = state =>
|
||||
export const failedStages = (state) =>
|
||||
state.stages
|
||||
.filter(stage => stage.status.text.toLowerCase() === states.failed)
|
||||
.map(stage => ({
|
||||
.filter((stage) => stage.status.text.toLowerCase() === states.failed)
|
||||
.map((stage) => ({
|
||||
...stage,
|
||||
jobs: stage.jobs.filter(job => job.status.text.toLowerCase() === states.failed),
|
||||
jobs: stage.jobs.filter((job) => job.status.text.toLowerCase() === states.failed),
|
||||
}));
|
||||
|
||||
export const failedJobsCount = state =>
|
||||
export const failedJobsCount = (state) =>
|
||||
state.stages.reduce(
|
||||
(acc, stage) => acc + stage.jobs.filter(j => j.status.text === states.failed).length,
|
||||
(acc, stage) => acc + stage.jobs.filter((j) => j.status.text === states.failed).length,
|
||||
0,
|
||||
);
|
||||
|
||||
export const jobsCount = state => state.stages.reduce((acc, stage) => acc + stage.jobs.length, 0);
|
||||
export const jobsCount = (state) => state.stages.reduce((acc, stage) => acc + stage.jobs.length, 0);
|
||||
|
|
|
|||
|
|
@ -34,7 +34,9 @@ export default {
|
|||
computed: {
|
||||
...mapState(['members', 'tableFields', 'tableAttrs', 'currentUserId', 'sourceId']),
|
||||
filteredFields() {
|
||||
return FIELDS.filter(field => this.tableFields.includes(field.key) && this.showField(field));
|
||||
return FIELDS.filter(
|
||||
(field) => this.tableFields.includes(field.key) && this.showField(field),
|
||||
);
|
||||
},
|
||||
userIsLoggedIn() {
|
||||
return this.currentUserId !== null;
|
||||
|
|
@ -56,7 +58,7 @@ export default {
|
|||
return false;
|
||||
}
|
||||
|
||||
return this.members.some(member => {
|
||||
return this.members.some((member) => {
|
||||
return (
|
||||
canRemove(member, this.sourceId) ||
|
||||
canResend(member) ||
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
export const findMember = (state, memberId) => state.members.find(member => member.id === memberId);
|
||||
export const findMember = (state, memberId) =>
|
||||
state.members.find((member) => member.id === memberId);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import produce from 'immer';
|
||||
import { flattenDeep, isNumber } from 'lodash';
|
||||
import { GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
|
||||
import { roundOffFloat } from '~/lib/utils/common_utils';
|
||||
|
|
@ -84,11 +85,13 @@ export default {
|
|||
metricData() {
|
||||
const originalMetricQuery = this.graphData.metrics[0];
|
||||
|
||||
const metricQuery = { ...originalMetricQuery };
|
||||
metricQuery.result[0].values = metricQuery.result[0].values.map(([x, y]) => [
|
||||
x,
|
||||
y + this.yOffset,
|
||||
]);
|
||||
const metricQuery = produce(originalMetricQuery, draftQuery => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
draftQuery.result[0].values = draftQuery.result[0].values.map(([x, y]) => [
|
||||
x,
|
||||
y + this.yOffset,
|
||||
]);
|
||||
});
|
||||
return {
|
||||
...this.graphData,
|
||||
type: panelTypes.LINE_CHART,
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ export const fetchDashboard = ({ state, commit, dispatch, getters }) => {
|
|||
}
|
||||
|
||||
return getDashboard(state.dashboardEndpoint, params)
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
dispatch('receiveMetricsDashboardSuccess', { response });
|
||||
/**
|
||||
* After the dashboard is fetched, there can be non-blocking invalid syntax
|
||||
|
|
@ -125,7 +125,7 @@ export const fetchDashboard = ({ state, commit, dispatch, getters }) => {
|
|||
*/
|
||||
dispatch('fetchDashboardValidationWarnings');
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
Sentry.captureException(error);
|
||||
|
||||
commit(types.SET_ALL_DASHBOARDS, error.response?.data?.all_dashboards ?? []);
|
||||
|
|
@ -185,9 +185,9 @@ export const fetchDashboardData = ({ state, dispatch, getters }) => {
|
|||
dispatch('fetchVariableMetricLabelValues', { defaultQueryParams });
|
||||
|
||||
const promises = [];
|
||||
state.dashboard.panelGroups.forEach(group => {
|
||||
group.panels.forEach(panel => {
|
||||
panel.metrics.forEach(metric => {
|
||||
state.dashboard.panelGroups.forEach((group) => {
|
||||
group.panels.forEach((panel) => {
|
||||
panel.metrics.forEach((metric) => {
|
||||
promises.push(dispatch('fetchPrometheusMetric', { metric, defaultQueryParams }));
|
||||
});
|
||||
});
|
||||
|
|
@ -231,10 +231,10 @@ export const fetchPrometheusMetric = (
|
|||
commit(types.REQUEST_METRIC_RESULT, { metricId: metric.metricId });
|
||||
|
||||
return getPrometheusQueryData(metric.prometheusEndpointPath, queryParams)
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
commit(types.RECEIVE_METRIC_RESULT_SUCCESS, { metricId: metric.metricId, data });
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
Sentry.captureException(error);
|
||||
|
||||
commit(types.RECEIVE_METRIC_RESULT_FAILURE, { metricId: metric.metricId, error });
|
||||
|
|
@ -251,15 +251,15 @@ export const fetchDeploymentsData = ({ state, dispatch }) => {
|
|||
}
|
||||
return axios
|
||||
.get(state.deploymentsEndpoint)
|
||||
.then(resp => resp.data)
|
||||
.then(response => {
|
||||
.then((resp) => resp.data)
|
||||
.then((response) => {
|
||||
if (!response || !response.deployments) {
|
||||
createFlash(s__('Metrics|Unexpected deployment data response from prometheus endpoint'));
|
||||
}
|
||||
|
||||
dispatch('receiveDeploymentsDataSuccess', response.deployments);
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
Sentry.captureException(error);
|
||||
dispatch('receiveDeploymentsDataFailure');
|
||||
createFlash(s__('Metrics|There was an error getting deployment information.'));
|
||||
|
|
@ -285,10 +285,10 @@ export const fetchEnvironmentsData = ({ state, dispatch }) => {
|
|||
states: [ENVIRONMENT_AVAILABLE_STATE],
|
||||
},
|
||||
})
|
||||
.then(resp =>
|
||||
.then((resp) =>
|
||||
parseEnvironmentsResponse(resp.data?.project?.data?.environments, state.projectPath),
|
||||
)
|
||||
.then(environments => {
|
||||
.then((environments) => {
|
||||
if (!environments) {
|
||||
createFlash(
|
||||
s__('Metrics|There was an error fetching the environments data, please try again'),
|
||||
|
|
@ -297,7 +297,7 @@ export const fetchEnvironmentsData = ({ state, dispatch }) => {
|
|||
|
||||
dispatch('receiveEnvironmentsDataSuccess', environments);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
Sentry.captureException(err);
|
||||
dispatch('receiveEnvironmentsDataFailure');
|
||||
createFlash(s__('Metrics|There was an error getting environments information.'));
|
||||
|
|
@ -326,16 +326,18 @@ export const fetchAnnotations = ({ state, dispatch, getters }) => {
|
|||
startingFrom: start,
|
||||
},
|
||||
})
|
||||
.then(resp => resp.data?.project?.environments?.nodes?.[0].metricsDashboard?.annotations.nodes)
|
||||
.then(
|
||||
(resp) => resp.data?.project?.environments?.nodes?.[0].metricsDashboard?.annotations.nodes,
|
||||
)
|
||||
.then(parseAnnotationsResponse)
|
||||
.then(annotations => {
|
||||
.then((annotations) => {
|
||||
if (!annotations) {
|
||||
createFlash(s__('Metrics|There was an error fetching annotations. Please try again.'));
|
||||
}
|
||||
|
||||
dispatch('receiveAnnotationsSuccess', annotations);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
Sentry.captureException(err);
|
||||
dispatch('receiveAnnotationsFailure');
|
||||
createFlash(s__('Metrics|There was an error getting annotations information.'));
|
||||
|
|
@ -363,7 +365,7 @@ export const fetchDashboardValidationWarnings = ({ state, dispatch, getters }) =
|
|||
dashboardPath,
|
||||
},
|
||||
})
|
||||
.then(resp => resp.data?.project?.environments?.nodes?.[0]?.metricsDashboard)
|
||||
.then((resp) => resp.data?.project?.environments?.nodes?.[0]?.metricsDashboard)
|
||||
.then(({ schemaValidationWarnings } = {}) => {
|
||||
const hasWarnings = schemaValidationWarnings && schemaValidationWarnings.length !== 0;
|
||||
/**
|
||||
|
|
@ -372,7 +374,7 @@ export const fetchDashboardValidationWarnings = ({ state, dispatch, getters }) =
|
|||
*/
|
||||
dispatch('receiveDashboardValidationWarningsSuccess', hasWarnings || false);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
Sentry.captureException(err);
|
||||
dispatch('receiveDashboardValidationWarningsFailure');
|
||||
createFlash(
|
||||
|
|
@ -437,9 +439,9 @@ export const duplicateSystemDashboard = ({ state }, payload) => {
|
|||
|
||||
return axios
|
||||
.post(state.dashboardsEndpoint, params)
|
||||
.then(response => response.data)
|
||||
.then(data => data.dashboard)
|
||||
.catch(error => {
|
||||
.then((response) => response.data)
|
||||
.then((data) => data.dashboard)
|
||||
.catch((error) => {
|
||||
Sentry.captureException(error);
|
||||
|
||||
const { response } = error;
|
||||
|
|
@ -466,7 +468,7 @@ export const fetchVariableMetricLabelValues = ({ state, commit }, { defaultQuery
|
|||
const { start_time, end_time } = defaultQueryParams;
|
||||
const optionsRequests = [];
|
||||
|
||||
state.variables.forEach(variable => {
|
||||
state.variables.forEach((variable) => {
|
||||
if (variable.type === VARIABLE_TYPES.metric_label_values) {
|
||||
const { prometheusEndpointPath, label } = variable.options;
|
||||
|
||||
|
|
@ -474,7 +476,7 @@ export const fetchVariableMetricLabelValues = ({ state, commit }, { defaultQuery
|
|||
start_time,
|
||||
end_time,
|
||||
})
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
commit(types.UPDATE_VARIABLE_METRIC_LABEL_VALUES, { variable, label, data });
|
||||
})
|
||||
.catch(() => {
|
||||
|
|
@ -512,7 +514,7 @@ export const fetchPanelPreview = ({ state, commit, dispatch }, panelPreviewYml)
|
|||
|
||||
dispatch('fetchPanelPreviewMetrics');
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
commit(types.RECEIVE_PANEL_PREVIEW_FAILURE, extractErrorMessage(error));
|
||||
});
|
||||
};
|
||||
|
|
@ -535,10 +537,10 @@ export const fetchPanelPreviewMetrics = ({ state, commit }) => {
|
|||
return getPrometheusQueryData(metric.prometheusEndpointPath, params, {
|
||||
cancelToken: cancelTokenSource.token,
|
||||
})
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
commit(types.RECEIVE_PANEL_PREVIEW_METRIC_RESULT_SUCCESS, { index, data });
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
Sentry.captureException(error);
|
||||
|
||||
commit(types.RECEIVE_PANEL_PREVIEW_METRIC_RESULT_FAILURE, { index, error });
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ import {
|
|||
normalizeCustomDashboardPath,
|
||||
} from './utils';
|
||||
|
||||
const metricsIdsInPanel = panel =>
|
||||
panel.metrics.filter(metric => metric.metricId && metric.result).map(metric => metric.metricId);
|
||||
const metricsIdsInPanel = (panel) =>
|
||||
panel.metrics
|
||||
.filter((metric) => metric.metricId && metric.result)
|
||||
.map((metric) => metric.metricId);
|
||||
|
||||
/**
|
||||
* Returns a reference to the currently selected dashboard
|
||||
|
|
@ -17,8 +19,8 @@ const metricsIdsInPanel = panel =>
|
|||
export const selectedDashboard = (state, getters) => {
|
||||
const { allDashboards } = state;
|
||||
return (
|
||||
allDashboards.find(d => d.path === getters.fullDashboardPath) ||
|
||||
allDashboards.find(d => d.default) ||
|
||||
allDashboards.find((d) => d.path === getters.fullDashboardPath) ||
|
||||
allDashboards.find((d) => d.default) ||
|
||||
null
|
||||
);
|
||||
};
|
||||
|
|
@ -32,15 +34,15 @@ export const selectedDashboard = (state, getters) => {
|
|||
* @returns {Function} A function that returns an array of
|
||||
* states in all the metric in the dashboard or group.
|
||||
*/
|
||||
export const getMetricStates = state => groupKey => {
|
||||
export const getMetricStates = (state) => (groupKey) => {
|
||||
let groups = state.dashboard.panelGroups;
|
||||
if (groupKey) {
|
||||
groups = groups.filter(group => group.key === groupKey);
|
||||
groups = groups.filter((group) => group.key === groupKey);
|
||||
}
|
||||
|
||||
const metricStates = groups.reduce((acc, group) => {
|
||||
group.panels.forEach(panel => {
|
||||
panel.metrics.forEach(metric => {
|
||||
group.panels.forEach((panel) => {
|
||||
panel.metrics.forEach((metric) => {
|
||||
if (metric.state) {
|
||||
acc.push(metric.state);
|
||||
}
|
||||
|
|
@ -64,15 +66,15 @@ export const getMetricStates = state => groupKey => {
|
|||
* metrics in the dashboard that contain results, optionally
|
||||
* filtered by group key.
|
||||
*/
|
||||
export const metricsWithData = state => groupKey => {
|
||||
export const metricsWithData = (state) => (groupKey) => {
|
||||
let groups = state.dashboard.panelGroups;
|
||||
if (groupKey) {
|
||||
groups = groups.filter(group => group.key === groupKey);
|
||||
groups = groups.filter((group) => group.key === groupKey);
|
||||
}
|
||||
|
||||
const res = [];
|
||||
groups.forEach(group => {
|
||||
group.panels.forEach(panel => {
|
||||
groups.forEach((group) => {
|
||||
group.panels.forEach((panel) => {
|
||||
res.push(...metricsIdsInPanel(panel));
|
||||
});
|
||||
});
|
||||
|
|
@ -89,7 +91,7 @@ export const metricsWithData = state => groupKey => {
|
|||
* https://gitlab.com/gitlab-org/gitlab/-/issues/28241
|
||||
* https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27447
|
||||
*/
|
||||
export const metricsSavedToDb = state => {
|
||||
export const metricsSavedToDb = (state) => {
|
||||
const metricIds = [];
|
||||
state.dashboard.panelGroups.forEach(({ panels }) => {
|
||||
panels.forEach(({ metrics }) => {
|
||||
|
|
@ -111,8 +113,8 @@ export const metricsSavedToDb = state => {
|
|||
* @param {Object} state
|
||||
* @returns {Array} List of environments
|
||||
*/
|
||||
export const filteredEnvironments = state =>
|
||||
state.environments.filter(env =>
|
||||
export const filteredEnvironments = (state) =>
|
||||
state.environments.filter((env) =>
|
||||
env.name.toLowerCase().includes((state.environmentsSearchTerm || '').trim().toLowerCase()),
|
||||
);
|
||||
|
||||
|
|
@ -125,7 +127,7 @@ export const filteredEnvironments = state =>
|
|||
* @param {Object} state
|
||||
* @returns {Array} modified array of links
|
||||
*/
|
||||
export const linksWithMetadata = state => {
|
||||
export const linksWithMetadata = (state) => {
|
||||
const metadata = {
|
||||
timeRange: state.timeRange,
|
||||
};
|
||||
|
|
@ -152,7 +154,7 @@ export const linksWithMetadata = state => {
|
|||
* in the format of {variables[key1]=value1, variables[key2]=value2}
|
||||
*/
|
||||
|
||||
export const getCustomVariablesParams = state =>
|
||||
export const getCustomVariablesParams = (state) =>
|
||||
state.variables.reduce((acc, variable) => {
|
||||
const { name, value } = variable;
|
||||
if (value !== null) {
|
||||
|
|
@ -168,5 +170,5 @@ export const getCustomVariablesParams = state =>
|
|||
* @param {Object} state
|
||||
* @returns {String} full dashboard path
|
||||
*/
|
||||
export const fullDashboardPath = state =>
|
||||
export const fullDashboardPath = (state) =>
|
||||
normalizeCustomDashboardPath(state.currentDashboard, state.customDashboardBasePath);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { LIST_KEY_PROJECT, SORT_FIELDS } from './constants';
|
||||
|
||||
export default isGroupPage => SORT_FIELDS.filter(f => f.key !== LIST_KEY_PROJECT || isGroupPage);
|
||||
export default (isGroupPage) =>
|
||||
SORT_FIELDS.filter((f) => f.key !== LIST_KEY_PROJECT || isGroupPage);
|
||||
|
||||
/**
|
||||
* A small util function that works out if the delete action has deleted the
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
filteredNamespaces() {
|
||||
return this.namespaces.filter(n => n.name.toLowerCase().includes(this.filter.toLowerCase()));
|
||||
return this.namespaces.filter((n) =>
|
||||
n.name.toLowerCase().includes(this.filter.toLowerCase()),
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -43,7 +45,7 @@ export default {
|
|||
loadGroups() {
|
||||
axios
|
||||
.get(this.endpoint)
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
this.namespaces = response.data.namespaces;
|
||||
})
|
||||
.catch(() => createFlash(__('There was a problem fetching groups.')));
|
||||
|
|
|
|||
|
|
@ -100,8 +100,8 @@ export default class UserTabs {
|
|||
bindEvents() {
|
||||
this.$parentEl
|
||||
.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]')
|
||||
.on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event))
|
||||
.on('click', '.gl-pagination a', event => this.changeProjectsPage(event));
|
||||
.on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', (event) => this.tabShown(event))
|
||||
.on('click', '.gl-pagination a', (event) => this.changeProjectsPage(event));
|
||||
|
||||
window.addEventListener('resize', () => this.onResize());
|
||||
}
|
||||
|
|
@ -212,17 +212,19 @@ export default class UserTabs {
|
|||
const calendarPath = $calendarWrap.data('calendarPath');
|
||||
|
||||
AjaxCache.retrieve(calendarPath)
|
||||
.then(data => UserTabs.renderActivityCalendar(data, $calendarWrap))
|
||||
.then((data) => UserTabs.renderActivityCalendar(data, $calendarWrap))
|
||||
.catch(() => {
|
||||
const cWrap = $calendarWrap[0];
|
||||
cWrap.querySelector('.spinner').classList.add('invisible');
|
||||
cWrap.querySelector('.user-calendar-error').classList.remove('invisible');
|
||||
cWrap.querySelector('.user-calendar-error .js-retry-load').addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
cWrap.querySelector('.user-calendar-error').classList.add('invisible');
|
||||
cWrap.querySelector('.spinner').classList.remove('invisible');
|
||||
this.loadActivityCalendar();
|
||||
});
|
||||
cWrap
|
||||
.querySelector('.user-calendar-error .js-retry-load')
|
||||
.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
cWrap.querySelector('.user-calendar-error').classList.add('invisible');
|
||||
cWrap.querySelector('.spinner').classList.remove('invisible');
|
||||
this.loadActivityCalendar();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export default class AccessDropdown {
|
|||
this.setSelectedItems([]);
|
||||
this.persistPreselectedItems();
|
||||
|
||||
this.noOneObj = this.accessLevelsData.find(level => level.id === ACCESS_LEVEL_NONE);
|
||||
this.noOneObj = this.accessLevelsData.find((level) => level.id === ACCESS_LEVEL_NONE);
|
||||
|
||||
this.initDropdown();
|
||||
}
|
||||
|
|
@ -45,7 +45,7 @@ export default class AccessDropdown {
|
|||
onHide();
|
||||
}
|
||||
},
|
||||
clicked: options => {
|
||||
clicked: (options) => {
|
||||
const { $el, e } = options;
|
||||
const item = options.selectedObj;
|
||||
const fossWithMergeAccess = !this.hasLicense && this.accessLevel === ACCESS_LEVELS.MERGE;
|
||||
|
|
@ -56,7 +56,7 @@ export default class AccessDropdown {
|
|||
// We're not multiselecting quite yet in "Merge" access dropdown, on FOSS:
|
||||
// remove all preselected items before selecting this item
|
||||
// https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37499
|
||||
this.accessLevelsData.forEach(level => {
|
||||
this.accessLevelsData.forEach((level) => {
|
||||
this.removeSelectedItem(level);
|
||||
});
|
||||
}
|
||||
|
|
@ -65,7 +65,7 @@ export default class AccessDropdown {
|
|||
if (this.noOneObj) {
|
||||
if (item.id === this.noOneObj.id && !fossWithMergeAccess) {
|
||||
// remove all others selected items
|
||||
this.accessLevelsData.forEach(level => {
|
||||
this.accessLevelsData.forEach((level) => {
|
||||
if (level.id !== item.id) {
|
||||
this.removeSelectedItem(level);
|
||||
}
|
||||
|
|
@ -109,7 +109,7 @@ export default class AccessDropdown {
|
|||
return;
|
||||
}
|
||||
|
||||
const persistedItems = itemsToPreselect.map(item => {
|
||||
const persistedItems = itemsToPreselect.map((item) => {
|
||||
const persistedItem = { ...item };
|
||||
persistedItem.persisted = true;
|
||||
return persistedItem;
|
||||
|
|
@ -123,7 +123,7 @@ export default class AccessDropdown {
|
|||
}
|
||||
|
||||
getSelectedItems() {
|
||||
return this.items.filter(item => !item._destroy);
|
||||
return this.items.filter((item) => !item._destroy);
|
||||
}
|
||||
|
||||
getAllSelectedItems() {
|
||||
|
|
@ -134,7 +134,7 @@ export default class AccessDropdown {
|
|||
getInputData() {
|
||||
const selectedItems = this.getAllSelectedItems();
|
||||
|
||||
const accessLevels = selectedItems.map(item => {
|
||||
const accessLevels = selectedItems.map((item) => {
|
||||
const obj = {};
|
||||
|
||||
if (typeof item.id !== 'undefined') {
|
||||
|
|
@ -288,12 +288,14 @@ export default class AccessDropdown {
|
|||
$dropdownToggleText.removeClass('is-default');
|
||||
|
||||
if (currentItems.length === 1 && currentItems[0].type === LEVEL_TYPES.ROLE) {
|
||||
const roleData = this.accessLevelsData.find(data => data.id === currentItems[0].access_level);
|
||||
const roleData = this.accessLevelsData.find(
|
||||
(data) => data.id === currentItems[0].access_level,
|
||||
);
|
||||
return roleData.text;
|
||||
}
|
||||
|
||||
const labelPieces = [];
|
||||
const counts = countBy(currentItems, item => item.type);
|
||||
const counts = countBy(currentItems, (item) => item.type);
|
||||
|
||||
if (counts[LEVEL_TYPES.ROLE] > 0) {
|
||||
labelPieces.push(n__('1 role', '%d roles', counts[LEVEL_TYPES.ROLE]));
|
||||
|
|
@ -336,7 +338,7 @@ export default class AccessDropdown {
|
|||
});
|
||||
} else {
|
||||
this.getDeployKeys(query)
|
||||
.then(deployKeysResponse => callback(this.consolidateData(deployKeysResponse.data)))
|
||||
.then((deployKeysResponse) => callback(this.consolidateData(deployKeysResponse.data)))
|
||||
.catch(() => createFlash({ message: __('Failed to load deploy keys.') }));
|
||||
}
|
||||
}
|
||||
|
|
@ -365,7 +367,7 @@ export default class AccessDropdown {
|
|||
/*
|
||||
* Build roles
|
||||
*/
|
||||
const roles = this.accessLevelsData.map(level => {
|
||||
const roles = this.accessLevelsData.map((level) => {
|
||||
/* eslint-disable no-param-reassign */
|
||||
// This re-assignment is intentional as
|
||||
// level.type property is being used in removeSelectedItem()
|
||||
|
|
@ -389,7 +391,7 @@ export default class AccessDropdown {
|
|||
/*
|
||||
* Build groups
|
||||
*/
|
||||
const groups = groupsResponse.map(group => ({
|
||||
const groups = groupsResponse.map((group) => ({
|
||||
...group,
|
||||
type: LEVEL_TYPES.GROUP,
|
||||
}));
|
||||
|
|
@ -398,8 +400,8 @@ export default class AccessDropdown {
|
|||
* Build users
|
||||
*/
|
||||
const users = selectedItems
|
||||
.filter(item => item.type === LEVEL_TYPES.USER)
|
||||
.map(item => {
|
||||
.filter((item) => item.type === LEVEL_TYPES.USER)
|
||||
.map((item) => {
|
||||
// Save identifiers for easy-checking more later
|
||||
map.push(LEVEL_TYPES.USER + item.user_id);
|
||||
|
||||
|
|
@ -414,7 +416,7 @@ export default class AccessDropdown {
|
|||
|
||||
// Has to be checked against server response
|
||||
// because the selected item can be in filter results
|
||||
usersResponse.forEach(response => {
|
||||
usersResponse.forEach((response) => {
|
||||
// Add is it has not been added
|
||||
if (map.indexOf(LEVEL_TYPES.USER + response.id) === -1) {
|
||||
const user = { ...response };
|
||||
|
|
@ -444,7 +446,7 @@ export default class AccessDropdown {
|
|||
}
|
||||
|
||||
if (this.deployKeysOnProtectedBranchesEnabled) {
|
||||
const deployKeys = deployKeysResponse.map(response => {
|
||||
const deployKeys = deployKeysResponse.map((response) => {
|
||||
const {
|
||||
id,
|
||||
fingerprint,
|
||||
|
|
|
|||
|
|
@ -43,6 +43,11 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
metadataLoading: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
loader: {
|
||||
repeat: 10,
|
||||
|
|
@ -92,7 +97,11 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<title-area :title="$options.i18n.CONTAINER_REGISTRY_TITLE" :info-messages="infoMessages">
|
||||
<title-area
|
||||
:title="$options.i18n.CONTAINER_REGISTRY_TITLE"
|
||||
:info-messages="infoMessages"
|
||||
:metadata-loading="metadataLoading"
|
||||
>
|
||||
<template #right-actions>
|
||||
<slot name="commands"></slot>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -242,6 +242,7 @@ export default {
|
|||
|
||||
<template v-else>
|
||||
<registry-header
|
||||
:metadata-loading="isLoading"
|
||||
:images-count="containerRepositoriesCount"
|
||||
:expiration-policy="config.expirationPolicy"
|
||||
:help-page-path="config.helpPagePath"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ const fetchpromises = {};
|
|||
const resolvers = {};
|
||||
|
||||
export function resolveCommit(commits, path, { resolve, entry }) {
|
||||
const commit = commits.find(c => c.filePath === `${path}/${entry.name}` && c.type === entry.type);
|
||||
const commit = commits.find(
|
||||
(c) => c.filePath === `${path}/${entry.name}` && c.type === entry.type,
|
||||
);
|
||||
|
||||
if (commit) {
|
||||
resolve(commit);
|
||||
|
|
@ -42,7 +44,7 @@ export function fetchLogsTree(client, path, offset, resolver = null) {
|
|||
.then(({ data: newData, headers }) => {
|
||||
const headerLogsOffset = headers['more-logs-offset'];
|
||||
const sourceData = client.readQuery({ query: commitsQuery });
|
||||
const data = produce(sourceData, draftState => {
|
||||
const data = produce(sourceData, (draftState) => {
|
||||
draftState.commits.push(...normalizeData(newData, path));
|
||||
});
|
||||
client.writeQuery({
|
||||
|
|
@ -50,7 +52,7 @@ export function fetchLogsTree(client, path, offset, resolver = null) {
|
|||
data,
|
||||
});
|
||||
|
||||
resolvers[path].forEach(r => resolveCommit(data.commits, path, r));
|
||||
resolvers[path].forEach((r) => resolveCommit(data.commits, path, r));
|
||||
|
||||
delete fetchpromises[path];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
|
||||
export const updateElementsVisibility = (selector, isVisible) => {
|
||||
document.querySelectorAll(selector).forEach(elem => elem.classList.toggle('hidden', !isVisible));
|
||||
document
|
||||
.querySelectorAll(selector)
|
||||
.forEach((elem) => elem.classList.toggle('hidden', !isVisible));
|
||||
};
|
||||
|
||||
export const updateFormAction = (selector, basePath, path) => {
|
||||
|
|
|
|||
|
|
@ -50,9 +50,13 @@ export default {
|
|||
$(this.$el).trigger('hidden.gl.dropdown');
|
||||
},
|
||||
getUpdateVariables(dropdownLabels) {
|
||||
const currentLabelIds = this.selectedLabels.map(label => label.id);
|
||||
const userAddedLabelIds = dropdownLabels.filter(label => label.set).map(label => label.id);
|
||||
const userRemovedLabelIds = dropdownLabels.filter(label => !label.set).map(label => label.id);
|
||||
const currentLabelIds = this.selectedLabels.map((label) => label.id);
|
||||
const userAddedLabelIds = dropdownLabels
|
||||
.filter((label) => label.set)
|
||||
.map((label) => label.id);
|
||||
const userRemovedLabelIds = dropdownLabels
|
||||
.filter((label) => !label.set)
|
||||
.map((label) => label.id);
|
||||
|
||||
const labelIds = difference(union(currentLabelIds, userAddedLabelIds), userRemovedLabelIds);
|
||||
|
||||
|
|
@ -116,7 +120,7 @@ export default {
|
|||
}
|
||||
|
||||
const issuableType = camelCase(this.issuableType);
|
||||
this.selectedLabels = data[mutationName]?.[issuableType]?.labels?.nodes?.map(label => ({
|
||||
this.selectedLabels = data[mutationName]?.[issuableType]?.labels?.nodes?.map((label) => ({
|
||||
...label,
|
||||
id: getIdFromGraphQLId(label.id),
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
export const parseUserIds = userIds => userIds.split(/\s*,\s*/g);
|
||||
export const parseUserIds = (userIds) => userIds.split(/\s*,\s*/g);
|
||||
|
||||
export const stringifyUserIds = userIds => userIds.join(',');
|
||||
export const stringifyUserIds = (userIds) => userIds.join(',');
|
||||
|
||||
export const getErrorMessages = error => [].concat(error?.response?.data?.message ?? error.message);
|
||||
export const getErrorMessages = (error) =>
|
||||
[].concat(error?.response?.data?.message ?? error.message);
|
||||
|
|
|
|||
|
|
@ -49,7 +49,8 @@ export default {
|
|||
label: s__('AlertManagement|Key'),
|
||||
thClass,
|
||||
tdClass,
|
||||
formatter: string => capitalizeFirstCharacter(convertToSentenceCase(splitCamelCase(string))),
|
||||
formatter: (string) =>
|
||||
capitalizeFirstCharacter(convertToSentenceCase(splitCamelCase(string))),
|
||||
},
|
||||
{
|
||||
key: 'value',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlAvatar, GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import { GlAvatar, GlSprintf, GlLink, GlSkeletonLoader } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
name: 'TitleArea',
|
||||
|
|
@ -7,6 +7,7 @@ export default {
|
|||
GlAvatar,
|
||||
GlSprintf,
|
||||
GlLink,
|
||||
GlSkeletonLoader,
|
||||
},
|
||||
props: {
|
||||
avatar: {
|
||||
|
|
@ -24,6 +25,11 @@ export default {
|
|||
default: () => [],
|
||||
required: false,
|
||||
},
|
||||
metadataLoading: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -68,13 +74,23 @@ export default {
|
|||
</div>
|
||||
|
||||
<div class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mt-3">
|
||||
<div
|
||||
v-for="(row, metadataIndex) in metadataSlots"
|
||||
:key="metadataIndex"
|
||||
class="gl-display-flex gl-align-items-center gl-mr-5"
|
||||
>
|
||||
<slot :name="row"></slot>
|
||||
</div>
|
||||
<template v-if="!metadataLoading">
|
||||
<div
|
||||
v-for="(row, metadataIndex) in metadataSlots"
|
||||
:key="metadataIndex"
|
||||
class="gl-display-flex gl-align-items-center gl-mr-5"
|
||||
>
|
||||
<slot :name="row"></slot>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="gl-w-full">
|
||||
<gl-skeleton-loader :width="200" :height="16" preserve-aspect-ratio="xMinYMax meet">
|
||||
<circle cx="6" cy="8" r="6" />
|
||||
<rect x="16" y="4" width="200" height="8" rx="4" />
|
||||
</gl-skeleton-loader>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="$slots['right-actions']" class="gl-mt-3">
|
||||
|
|
|
|||
|
|
@ -62,7 +62,9 @@ export default {
|
|||
return files.every(this.isFileValid);
|
||||
},
|
||||
isValidDragDataType({ dataTransfer }) {
|
||||
return Boolean(dataTransfer && dataTransfer.types.some(t => t === VALID_DATA_TRANSFER_TYPE));
|
||||
return Boolean(
|
||||
dataTransfer && dataTransfer.types.some((t) => t === VALID_DATA_TRANSFER_TYPE),
|
||||
);
|
||||
},
|
||||
ondrop({ dataTransfer = {} }) {
|
||||
this.dragCounter = 0;
|
||||
|
|
|
|||
|
|
@ -54,5 +54,6 @@ export const timeRanges = [
|
|||
},
|
||||
];
|
||||
|
||||
export const defaultTimeRange = timeRanges.find(tr => tr.default);
|
||||
export const getTimeWindow = timeWindowName => timeRanges.find(tr => tr.name === timeWindowName);
|
||||
export const defaultTimeRange = timeRanges.find((tr) => tr.default);
|
||||
export const getTimeWindow = (timeWindowName) =>
|
||||
timeRanges.find((tr) => tr.name === timeWindowName);
|
||||
|
|
|
|||
|
|
@ -51,17 +51,17 @@ module Mutations
|
|||
|
||||
params = scalars.with_indifferent_access
|
||||
|
||||
release_result = ::Releases::UpdateService.new(project, current_user, params).execute
|
||||
result = ::Releases::UpdateService.new(project, current_user, params).execute
|
||||
|
||||
if release_result[:status] == :success
|
||||
if result[:status] == :success
|
||||
{
|
||||
release: release_result[:release],
|
||||
release: result[:release],
|
||||
errors: []
|
||||
}
|
||||
else
|
||||
{
|
||||
release: nil,
|
||||
errors: [release_result[:message]]
|
||||
errors: [result[:message]]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ module Resolvers
|
|||
include Gitlab::Graphql::Authorize::AuthorizeResource
|
||||
|
||||
type Types::Projects::Services::JiraProjectType.connection_type, null: true
|
||||
authorize :admin_project
|
||||
|
||||
argument :name,
|
||||
GraphQL::STRING_TYPE,
|
||||
|
|
@ -31,10 +32,6 @@ module Resolvers
|
|||
end
|
||||
end
|
||||
|
||||
def authorized_resource?(project)
|
||||
Ability.allowed?(context[:current_user], :admin_project, project)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
alias_method :jira_service, :object
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Resolvers
|
||||
class ReleaseMilestonesResolver < BaseResolver
|
||||
type Types::MilestoneType.connection_type, null: true
|
||||
|
||||
alias_method :release, :object
|
||||
|
||||
def resolve(**args)
|
||||
offset_pagination(release.milestones.order_by_dates_and_title)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -35,7 +35,8 @@ module Types
|
|||
field :links, Types::ReleaseLinksType, null: true, method: :itself,
|
||||
description: 'Links of the release'
|
||||
field :milestones, Types::MilestoneType.connection_type, null: true,
|
||||
description: 'Milestones associated to the release'
|
||||
description: 'Milestones associated to the release',
|
||||
resolver: ::Resolvers::ReleaseMilestonesResolver
|
||||
field :evidences, Types::EvidenceType.connection_type, null: true,
|
||||
description: 'Evidence for the release'
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ class Milestone < ApplicationRecord
|
|||
scope :order_by_name_asc, -> { order(Arel::Nodes::Ascending.new(arel_table[:title].lower)) }
|
||||
scope :reorder_by_due_date_asc, -> { reorder(Gitlab::Database.nulls_last_order('due_date', 'ASC')) }
|
||||
scope :with_api_entity_associations, -> { preload(project: [:project_feature, :route, namespace: :route]) }
|
||||
scope :order_by_dates_and_title, -> { order(due_date: :asc, start_date: :asc, title: :asc) }
|
||||
|
||||
validates_associated :milestone_releases, message: -> (_, obj) { obj[:value].map(&:errors).map(&:full_messages).join(",") }
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ class Release < ApplicationRecord
|
|||
end
|
||||
|
||||
def milestone_titles
|
||||
self.milestones.map {|m| m.title }.sort.join(", ")
|
||||
self.milestones.order_by_dates_and_title.map {|m| m.title }.join(', ')
|
||||
end
|
||||
|
||||
def to_hook_data(action)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Adjust container registry metadata during loading
|
||||
merge_request: 50181
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Fix project transfer corrupting shared runners state
|
||||
merge_request: 47316
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Return release milestones in predictable order
|
||||
merge_request: 47700
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -9499,6 +9499,11 @@ type GeoNode {
|
|||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Global ID of a specific compliance framework to return.
|
||||
"""
|
||||
id: ComplianceManagementFrameworkID
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
|
|
@ -9715,7 +9720,7 @@ type Group {
|
|||
): CodeCoverageActivityConnection
|
||||
|
||||
"""
|
||||
Compliance frameworks available to projects in this namespace Available only
|
||||
Compliance frameworks available to projects in this namespace. Available only
|
||||
when feature flag `ff_custom_compliance_frameworks` is enabled.
|
||||
"""
|
||||
complianceFrameworks(
|
||||
|
|
@ -15368,7 +15373,7 @@ type Namespace {
|
|||
additionalPurchasedStorageSize: Float
|
||||
|
||||
"""
|
||||
Compliance frameworks available to projects in this namespace Available only
|
||||
Compliance frameworks available to projects in this namespace. Available only
|
||||
when feature flag `ff_custom_compliance_frameworks` is enabled.
|
||||
"""
|
||||
complianceFrameworks(
|
||||
|
|
@ -15387,6 +15392,11 @@ type Namespace {
|
|||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Global ID of a specific compliance framework to return.
|
||||
"""
|
||||
id: ComplianceManagementFrameworkID
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -26177,6 +26177,16 @@
|
|||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"description": "Global ID of a specific compliance framework to return.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ComplianceManagementFrameworkID",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
|
|
@ -26928,7 +26938,7 @@
|
|||
},
|
||||
{
|
||||
"name": "complianceFrameworks",
|
||||
"description": "Compliance frameworks available to projects in this namespace Available only when feature flag `ff_custom_compliance_frameworks` is enabled.",
|
||||
"description": "Compliance frameworks available to projects in this namespace. Available only when feature flag `ff_custom_compliance_frameworks` is enabled.",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
|
|
@ -45542,7 +45552,7 @@
|
|||
},
|
||||
{
|
||||
"name": "complianceFrameworks",
|
||||
"description": "Compliance frameworks available to projects in this namespace Available only when feature flag `ff_custom_compliance_frameworks` is enabled.",
|
||||
"description": "Compliance frameworks available to projects in this namespace. Available only when feature flag `ff_custom_compliance_frameworks` is enabled.",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
|
|
@ -45583,6 +45593,16 @@
|
|||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"description": "Global ID of a specific compliance framework to return.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ComplianceManagementFrameworkID",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
|
|
|
|||
|
|
@ -1579,7 +1579,7 @@ Represents an external issue.
|
|||
| `board` | Board | A single board of the group |
|
||||
| `boards` | BoardConnection | Boards of the group |
|
||||
| `codeCoverageActivities` | CodeCoverageActivityConnection | Represents the code coverage activity for this group |
|
||||
| `complianceFrameworks` | ComplianceFrameworkConnection | Compliance frameworks available to projects in this namespace Available only when feature flag `ff_custom_compliance_frameworks` is enabled. |
|
||||
| `complianceFrameworks` | ComplianceFrameworkConnection | Compliance frameworks available to projects in this namespace. Available only when feature flag `ff_custom_compliance_frameworks` is enabled. |
|
||||
| `containerRepositories` | ContainerRepositoryConnection | Container repositories of the group |
|
||||
| `containerRepositoriesCount` | Int! | Number of container repositories in the group |
|
||||
| `containsLockedProjects` | Boolean! | Includes at least one project where the repository size exceeds the limit |
|
||||
|
|
@ -2329,7 +2329,7 @@ Contains statistics about a milestone.
|
|||
| ----- | ---- | ----------- |
|
||||
| `actualRepositorySizeLimit` | Float | Size limit for repositories in the namespace in bytes |
|
||||
| `additionalPurchasedStorageSize` | Float | Additional storage purchased for the root namespace in bytes |
|
||||
| `complianceFrameworks` | ComplianceFrameworkConnection | Compliance frameworks available to projects in this namespace Available only when feature flag `ff_custom_compliance_frameworks` is enabled. |
|
||||
| `complianceFrameworks` | ComplianceFrameworkConnection | Compliance frameworks available to projects in this namespace. Available only when feature flag `ff_custom_compliance_frameworks` is enabled. |
|
||||
| `containsLockedProjects` | Boolean! | Includes at least one project where the repository size exceeds the limit |
|
||||
| `description` | String | Description of the namespace |
|
||||
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
|
||||
|
|
|
|||
|
|
@ -7,15 +7,42 @@ type: reference, api
|
|||
|
||||
# Merge requests API
|
||||
|
||||
> - `author_id`, `author_username`, and `assignee_id` were [introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/13060) in GitLab 9.5.
|
||||
> - `my_reaction_emoji` was [introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/14016) in GitLab 10.0.
|
||||
> - For the `scope` attribute, `created-by-me` and `assigned-to-me` were [deprecated](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/18935) in favor of `created_by_me` and `assigned_to_me` in GitLab 11.0.
|
||||
> - `with_labels_details` was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21413) in GitLab 12.7.
|
||||
> - `author_username` and `author_username` were [introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/13060) in GitLab 12.10.
|
||||
> - `reference` was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20354) in GitLab 12.10 in favour of `references`.
|
||||
> - `with_merge_status_recheck` was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31890) in GitLab 13.0.
|
||||
|
||||
Every API call to merge requests must be authenticated.
|
||||
|
||||
WARNING:
|
||||
> `reference` attribute in response is deprecated in favour of `references`.
|
||||
> Introduced [GitLab 12.6](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20354)
|
||||
**Important notes:**
|
||||
|
||||
NOTE:
|
||||
> `references.relative` is relative to the group / project that the merge request is being requested. When merge request is fetched from its project
|
||||
> `relative` format would be the same as `short` format and when requested across groups / projects it is expected to be the same as `full` format.
|
||||
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/29984) in GitLab 12.8, the mergeability (`merge_status`)
|
||||
of each merge request is checked asynchronously when a request is made to this endpoint. Poll this API endpoint
|
||||
to get updated status. This affects the `has_conflicts` property as it is dependent on the `merge_status`. It returns
|
||||
`false` unless `merge_status` is `cannot_be_merged`.
|
||||
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31890) in GitLab 13.0, listing merge requests may
|
||||
not proactively update `merge_status` (which also affects the `has_conflicts`), as this can be an expensive operation.
|
||||
If you need the value of these fields from this endpoint, set the `with_merge_status_recheck` parameter to
|
||||
`true` in the query.
|
||||
- `references.relative` is relative to the group or project that the merge request is being requested. When the merge request
|
||||
is fetched from its project, `relative` format would be the same as `short` format, and when requested across groups or projects, it is expected to be the same as `full` format.
|
||||
- If `approvals_before_merge` **(STARTER)** is not provided, it inherits the value from the target project. If provided, the following conditions must hold for it to take effect:
|
||||
|
||||
- The target project's `approvals_before_merge` must be greater than zero. A
|
||||
value of zero disables approvals for that project.
|
||||
- The provided value of `approvals_before_merge` must be greater than the
|
||||
target project's `approvals_before_merge`.
|
||||
|
||||
This API returns `HTTP 201 Created` for a successful response.
|
||||
|
||||
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46190) in GitLab 13.6,
|
||||
diffs associated with the set of changes have the same size limitations applied as other diffs
|
||||
returned by the API or viewed via the UI. When these limits impact the results, the `overflow`
|
||||
field contains a value of `true`. Diff data without these limits applied can be retrieved by
|
||||
adding the `access_raw_diffs` parameter, but it is slower and more resource-intensive.
|
||||
|
||||
## List merge requests
|
||||
|
||||
|
|
@ -26,7 +53,7 @@ default it returns only merge requests created by the current user. To
|
|||
get all merge requests, use parameter `scope=all`.
|
||||
|
||||
The `state` parameter can be used to get only merge requests with a
|
||||
given state (`opened`, `closed`, `locked`, or `merged`) or all of them (`all`). It should be noted that when searching by `locked` it will mostly return no results as it is a short-lived, transitional state.
|
||||
given state (`opened`, `closed`, `locked`, or `merged`) or all of them (`all`). It should be noted that when searching by `locked` it mostly returns no results as it is a short-lived, transitional state.
|
||||
The pagination parameters `page` and `per_page` can be used to
|
||||
restrict the list of merge requests.
|
||||
|
||||
|
|
@ -47,50 +74,35 @@ Parameters:
|
|||
|
||||
| Attribute | Type | Required | Description |
|
||||
| ------------------------------- | -------------- | -------- | ---------------------------------------------------------------------------------------------------------------------- |
|
||||
| `state` | string | no | Return all merge requests or just those that are `opened`, `closed`, `locked`, or `merged` |
|
||||
| `order_by` | string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
|
||||
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
|
||||
| `state` | string | no | Return all merge requests or just those that are `opened`, `closed`, `locked`, or `merged`. |
|
||||
| `order_by` | string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`. |
|
||||
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc`. |
|
||||
| `milestone` | string | no | Return merge requests for a specific milestone. `None` returns merge requests with no milestone. `Any` returns merge requests that have an assigned milestone. |
|
||||
| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request |
|
||||
| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request. |
|
||||
| `labels` | string | no | Return merge requests matching a comma separated list of labels. `None` lists all merge requests with no labels. `Any` lists all merge requests with at least one label. `No+Label` (Deprecated) lists all merge requests with no labels. Predefined names are case-insensitive. |
|
||||
| `with_labels_details` | boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. Introduced in [GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21413) |
|
||||
| `with_merge_status_recheck` | boolean | no | If `true`, this projection requests (but does not guarantee) that the `merge_status` field be recalculated asynchronously. Default is `false`. Introduced in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31890) |
|
||||
| `with_labels_details` | boolean | no | If `true`, response returns more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. |
|
||||
| `with_merge_status_recheck` | boolean | no | If `true`, this projection requests (but does not guarantee) that the `merge_status` field be recalculated asynchronously. Default is `false`. |
|
||||
| `created_after` | datetime | no | Return merge requests created on or after the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
|
||||
| `created_before` | datetime | no | Return merge requests created on or before the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
|
||||
| `updated_after` | datetime | no | Return merge requests updated on or after the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
|
||||
| `updated_before` | datetime | no | Return merge requests updated on or before the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
|
||||
| `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead. |
|
||||
| `author_id` | integer | no | Returns merge requests created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`
|
||||
| `author_username` | string | no | Returns merge requests created by the given `username`. Mutually exclusive with `author_id`. _([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/13060) in GitLab 12.10)_ | |
|
||||
| `author_id` | integer | no | Returns merge requests created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`.
|
||||
| `author_username` | string | no | Returns merge requests created by the given `username`. Mutually exclusive with `author_id`. | |
|
||||
| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id`. `None` returns unassigned merge requests. `Any` returns merge requests with an assignee. |
|
||||
| `approver_ids` **(STARTER)** | integer array | no | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. |
|
||||
| `approved_by_ids` **(STARTER)** | integer array | no | Returns merge requests which have been approved by all the users with the given `id`s (Max: 5). `None` returns merge requests with no approvals. `Any` returns merge requests with an approval. |
|
||||
| `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/14016) in GitLab 10.0)_ |
|
||||
| `source_branch` | string | no | Return merge requests with the given source branch |
|
||||
| `target_branch` | string | no | Return merge requests with the given target branch |
|
||||
| `search` | string | no | Search merge requests against their `title` and `description` |
|
||||
| `in` | string | no | Modify the scope of the `search` attribute. `title`, `description`, or a string joining them with comma. Default is `title,description` |
|
||||
| `wip` | string | no | Filter merge requests against their `wip` status. `yes` to return *only* WIP merge requests, `no` to return *non* WIP merge requests |
|
||||
| `not` | Hash | no | Return merge requests that do not match the parameters supplied. Accepts: `labels`, `milestone`, `author_id`, `author_username`, `assignee_id`, `assignee_username`, `my_reaction_emoji` |
|
||||
| `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. |
|
||||
| `source_branch` | string | no | Return merge requests with the given source branch. |
|
||||
| `target_branch` | string | no | Return merge requests with the given target branch. |
|
||||
| `search` | string | no | Search merge requests against their `title` and `description`. |
|
||||
| `in` | string | no | Modify the scope of the `search` attribute. `title`, `description`, or a string joining them with comma. Default is `title,description`. |
|
||||
| `wip` | string | no | Filter merge requests against their `wip` status. `yes` to return *only* WIP merge requests, `no` to return *non* WIP merge requests. |
|
||||
| `not` | Hash | no | Return merge requests that do not match the parameters supplied. Accepts: `labels`, `milestone`, `author_id`, `author_username`, `assignee_id`, `assignee_username`, `my_reaction_emoji`. |
|
||||
| `environment` | string | no | Returns merge requests deployed to the given environment. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
|
||||
| `deployed_before` | datetime | no | Return merge requests deployed before the given date/time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
|
||||
| `deployed_after` | datetime | no | Return merge requests deployed after the given date/time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
|
||||
|
||||
NOTE:
|
||||
[Starting in GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31890),
|
||||
listing merge requests may not proactively update the `merge_status` field
|
||||
(which also affects the `has_conflicts` field), as this can be an expensive
|
||||
operation. If you are interested in the value of these fields from this
|
||||
endpoint, set the `with_merge_status_recheck` parameter to `true` in the query.
|
||||
|
||||
NOTE:
|
||||
[Starting in GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/issues/29984),
|
||||
the mergeability (`merge_status`) of each merge request will be checked
|
||||
asynchronously when a request is made to this endpoint. Poll this API endpoint
|
||||
to get updated status. This affects the `has_conflicts` property as it is
|
||||
dependent on the `merge_status`. It'll return `false` unless `merge_status` is
|
||||
`cannot_be_merged`.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
|
|
@ -193,7 +205,7 @@ dependent on the `merge_status`. It'll return `false` unless `merge_status` is
|
|||
]
|
||||
```
|
||||
|
||||
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) will also see
|
||||
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) also see
|
||||
the `approvals_before_merge` parameter:
|
||||
|
||||
```json
|
||||
|
|
@ -224,43 +236,43 @@ GET /projects/:id/merge_requests?my_reaction_emoji=star
|
|||
```
|
||||
|
||||
`project_id` represents the ID of the project where the MR resides.
|
||||
`project_id` will always equal `target_project_id`.
|
||||
`project_id` always equals `target_project_id`.
|
||||
|
||||
In the case of a merge request from the same project,
|
||||
`source_project_id`, `target_project_id` and `project_id`
|
||||
will be the same. In the case of a merge request from a fork,
|
||||
`target_project_id` and `project_id` will be the same and
|
||||
`source_project_id` will be the fork project's ID.
|
||||
are the same. In the case of a merge request from a fork,
|
||||
`target_project_id` and `project_id` are the same and
|
||||
`source_project_id` is the fork project's ID.
|
||||
|
||||
Parameters:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| ------------------------------- | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `id` | integer | yes | The ID of a project |
|
||||
| `iids[]` | integer array | no | Return the request having the given `iid` |
|
||||
| `state` | string | no | Return all merge requests or just those that are `opened`, `closed`, `locked`, or `merged` |
|
||||
| `order_by` | string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
|
||||
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
|
||||
| `id` | integer | yes | The ID of a project. |
|
||||
| `iids[]` | integer array | no | Return the request having the given `iid`. |
|
||||
| `state` | string | no | Return all merge requests or just those that are `opened`, `closed`, `locked`, or `merged`. |
|
||||
| `order_by` | string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`. |
|
||||
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc`. |
|
||||
| `milestone` | string | no | Return merge requests for a specific milestone. `None` returns merge requests with no milestone. `Any` returns merge requests that have an assigned milestone. |
|
||||
| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request |
|
||||
| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request. |
|
||||
| `labels` | string | no | Return merge requests matching a comma separated list of labels. `None` lists all merge requests with no labels. `Any` lists all merge requests with at least one label. `No+Label` (Deprecated) lists all merge requests with no labels. Predefined names are case-insensitive. |
|
||||
| `with_labels_details` | boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. Introduced in [GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21413) |
|
||||
| `with_merge_status_recheck` | boolean | no | If `true`, this projection requests (but does not guarantee) that the `merge_status` field be recalculated asynchronously. Default is `false`. Introduced in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31890) |
|
||||
| `with_labels_details` | boolean | no | If `true`, response returns more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. |
|
||||
| `with_merge_status_recheck` | boolean | no | If `true`, this projection requests (but does not guarantee) that the `merge_status` field be recalculated asynchronously. Default is `false`. |
|
||||
| `created_after` | datetime | no | Return merge requests created on or after the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
|
||||
| `created_before` | datetime | no | Return merge requests created on or before the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
|
||||
| `updated_after` | datetime | no | Return merge requests updated on or after the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
|
||||
| `updated_before` | datetime | no | Return merge requests updated on or before the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
|
||||
| `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/13060) in GitLab 9.5. [Changed to snake_case](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/18935) in GitLab 11.0)_ |
|
||||
| `author_id` | integer | no | Returns merge requests created by the given user `id`. Mutually exclusive with `author_username`. _([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/13060) in GitLab 9.5)_
|
||||
| `author_username` | string | no | Returns merge requests created by the given `username`. Mutually exclusive with `author_id`. _([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/13060) in GitLab 12.10)_ | |
|
||||
| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id`. `None` returns unassigned merge requests. `Any` returns merge requests with an assignee. _([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/13060) in GitLab 9.5)_ |
|
||||
| `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me`, or `all`. |
|
||||
| `author_id` | integer | no | Returns merge requests created by the given user `id`. Mutually exclusive with `author_username`. |
|
||||
| `author_username` | string | no | Returns merge requests created by the given `username`. Mutually exclusive with `author_id`.|
|
||||
| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id`. `None` returns unassigned merge requests. `Any` returns merge requests with an assignee. |
|
||||
| `approver_ids` **(STARTER)** | integer array | no | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. |
|
||||
| `approved_by_ids` **(STARTER)** | integer array | no | Returns merge requests which have been approved by all the users with the given `id`s (Max: 5). `None` returns merge requests with no approvals. `Any` returns merge requests with an approval. |
|
||||
| `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/14016) in GitLab 10.0)_ |
|
||||
| `source_branch` | string | no | Return merge requests with the given source branch |
|
||||
| `target_branch` | string | no | Return merge requests with the given target branch |
|
||||
| `search` | string | no | Search merge requests against their `title` and `description` |
|
||||
| `wip` | string | no | Filter merge requests against their `wip` status. `yes` to return *only* WIP merge requests, `no` to return *non* WIP merge requests |
|
||||
| `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. |
|
||||
| `source_branch` | string | no | Return merge requests with the given source branch. |
|
||||
| `target_branch` | string | no | Return merge requests with the given target branch. |
|
||||
| `search` | string | no | Search merge requests against their `title` and `description`. |
|
||||
| `wip` | string | no | Filter merge requests against their `wip` status. `yes` to return *only* WIP merge requests, `no` to return *non* WIP merge requests. |
|
||||
|
||||
```json
|
||||
[
|
||||
|
|
@ -366,7 +378,7 @@ Parameters:
|
|||
]
|
||||
```
|
||||
|
||||
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) will also see
|
||||
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) also see
|
||||
the `approvals_before_merge` parameter:
|
||||
|
||||
```json
|
||||
|
|
@ -401,30 +413,30 @@ Parameters:
|
|||
|
||||
| Attribute | Type | Required | Description |
|
||||
| ------------------------------- | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `id` | integer | yes | The ID of a group |
|
||||
| `state` | string | no | Return all merge requests or just those that are `opened`, `closed`, `locked`, or `merged` |
|
||||
| `order_by` | string | no | Return merge requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
|
||||
| `sort` | string | no | Return merge requests sorted in `asc` or `desc` order. Default is `desc` |
|
||||
| `id` | integer | yes | The ID of a group. |
|
||||
| `state` | string | no | Return all merge requests or just those that are `opened`, `closed`, `locked`, or `merged`. |
|
||||
| `order_by` | string | no | Return merge requests ordered by `created_at` or `updated_at` fields. Default is `created_at`. |
|
||||
| `sort` | string | no | Return merge requests sorted in `asc` or `desc` order. Default is `desc`. |
|
||||
| `milestone` | string | no | Return merge requests for a specific milestone. `None` returns merge requests with no milestone. `Any` returns merge requests that have an assigned milestone. |
|
||||
| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request |
|
||||
| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request. |
|
||||
| `labels` | string | no | Return merge requests matching a comma separated list of labels. `None` lists all merge requests with no labels. `Any` lists all merge requests with at least one label. `No+Label` (Deprecated) lists all merge requests with no labels. Predefined names are case-insensitive. |
|
||||
| `with_labels_details` | boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. Introduced in [GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21413)|
|
||||
| `with_merge_status_recheck` | boolean | no | If `true`, this projection requests (but does not guarantee) that the `merge_status` field be recalculated asynchronously. Default is `false`. Introduced in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31890) |
|
||||
| `created_after` | datetime | no | Return merge requests created on or after the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
|
||||
| `created_before` | datetime | no | Return merge requests created on or before the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
|
||||
| `updated_after` | datetime | no | Return merge requests updated on or after the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
|
||||
| `updated_before` | datetime | no | Return merge requests updated on or before the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
|
||||
| `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> |
|
||||
| `author_id` | integer | no | Returns merge requests created by the given user `id`. Mutually exclusive with `author_username`. _([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/13060) in GitLab 9.5)_
|
||||
| `author_username` | string | no | Returns merge requests created by the given `username`. Mutually exclusive with `author_id`. _([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/13060) in GitLab 12.10)_ | |
|
||||
| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id`. `None` returns unassigned merge requests. `Any` returns merge requests with an assignee. _([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/13060) in GitLab 9.5)_ |
|
||||
| `with_labels_details` | boolean | no | If `true`, response returns more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. Introduced in [GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21413).|
|
||||
| `with_merge_status_recheck` | boolean | no | If `true`, this projection requests (but does not guarantee) that the `merge_status` field be recalculated asynchronously. Default is `false`. Introduced in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31890). |
|
||||
| `created_after` | datetime | no | Return merge requests created on or after the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
|
||||
| `created_before` | datetime | no | Return merge requests created on or before the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
|
||||
| `updated_after` | datetime | no | Return merge requests updated on or after the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
|
||||
| `updated_before` | datetime | no | Return merge requests updated on or before the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
|
||||
| `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`. |
|
||||
| `author_id` | integer | no | Returns merge requests created by the given user `id`. Mutually exclusive with `author_username`. _([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/13060) in GitLab 9.5)_.
|
||||
| `author_username` | string | no | Returns merge requests created by the given `username`. Mutually exclusive with `author_id`. _([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/13060) in GitLab 12.10)_. | |
|
||||
| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id`. `None` returns unassigned merge requests. `Any` returns merge requests with an assignee. _([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/13060) in GitLab 9.5)_. |
|
||||
| `approver_ids` **(STARTER)** | integer array | no | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. |
|
||||
| `approved_by_ids` **(STARTER)** | integer array | no | Returns merge requests which have been approved by all the users with the given `id`s (Max: 5). `None` returns merge requests with no approvals. `Any` returns merge requests with an approval. |
|
||||
| `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/14016) in GitLab 10.0)_ |
|
||||
| `source_branch` | string | no | Return merge requests with the given source branch |
|
||||
| `target_branch` | string | no | Return merge requests with the given target branch |
|
||||
| `search` | string | no | Search merge requests against their `title` and `description` |
|
||||
| `non_archived` | boolean | no | Return merge requests from non archived projects only. Default is true. _(Introduced in [GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23809))_ |
|
||||
| `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/14016) in GitLab 10.0)_. |
|
||||
| `source_branch` | string | no | Return merge requests with the given source branch. |
|
||||
| `target_branch` | string | no | Return merge requests with the given target branch. |
|
||||
| `search` | string | no | Search merge requests against their `title` and `description`. |
|
||||
| `non_archived` | boolean | no | Return merge requests from non archived projects only. Default is true. _(Introduced in [GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23809))_. |
|
||||
|
||||
```json
|
||||
[
|
||||
|
|
@ -528,7 +540,7 @@ Parameters:
|
|||
]
|
||||
```
|
||||
|
||||
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) will also see
|
||||
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) also see
|
||||
the `approvals_before_merge` parameter:
|
||||
|
||||
```json
|
||||
|
|
@ -548,7 +560,7 @@ Shows information about a single merge request.
|
|||
|
||||
**Note**: the `changes_count` value in the response is a string, not an
|
||||
integer. This is because when an MR has too many changes to display and store,
|
||||
it will be capped at 1,000. In that case, the API will return the string
|
||||
it is capped at 1,000. In that case, the API returns the string
|
||||
`"1000+"` for the changes count.
|
||||
|
||||
```plaintext
|
||||
|
|
@ -557,19 +569,11 @@ GET /projects/:id/merge_requests/:merge_request_iid
|
|||
|
||||
Parameters:
|
||||
|
||||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
|
||||
- `merge_request_iid` (required) - The internal ID of the merge request
|
||||
- `render_html` (optional) - If `true` response includes rendered HTML for title and description
|
||||
- `include_diverged_commits_count` (optional) - If `true` response includes the commits behind the target branch
|
||||
- `include_rebase_in_progress` (optional) - If `true` response includes whether a rebase operation is in progress
|
||||
|
||||
NOTE:
|
||||
[Starting in GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/issues/29984),
|
||||
the mergeability (`merge_status`) of a merge request will be checked
|
||||
asynchronously when a request is made to this endpoint. Poll this API endpoint
|
||||
to get updated status. This affects the `has_conflicts` property as it is
|
||||
dependent on the `merge_status`. It'll return `false` unless `merge_status` is
|
||||
`cannot_be_merged`.
|
||||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user.
|
||||
- `merge_request_iid` (required) - The internal ID of the merge request.
|
||||
- `render_html` (optional) - If `true` response includes rendered HTML for title and description.
|
||||
- `include_diverged_commits_count` (optional) - If `true` response includes the commits behind the target branch.
|
||||
- `include_rebase_in_progress` (optional) - If `true` response includes whether a rebase operation is in progress.
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -697,7 +701,7 @@ dependent on the `merge_status`. It'll return `false` unless `merge_status` is
|
|||
}
|
||||
```
|
||||
|
||||
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) will also see
|
||||
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) also see
|
||||
the `approvals_before_merge` parameter:
|
||||
|
||||
```json
|
||||
|
|
@ -719,8 +723,8 @@ GET /projects/:id/merge_requests/:merge_request_iid/participants
|
|||
|
||||
Parameters:
|
||||
|
||||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
|
||||
- `merge_request_iid` (required) - The internal ID of the merge request
|
||||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user.
|
||||
- `merge_request_iid` (required) - The internal ID of the merge request.
|
||||
|
||||
```json
|
||||
[
|
||||
|
|
@ -753,8 +757,8 @@ GET /projects/:id/merge_requests/:merge_request_iid/commits
|
|||
|
||||
Parameters:
|
||||
|
||||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
|
||||
- `merge_request_iid` (required) - The internal ID of the merge request
|
||||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user.
|
||||
- `merge_request_iid` (required) - The internal ID of the merge request.
|
||||
|
||||
```json
|
||||
[
|
||||
|
|
@ -787,12 +791,6 @@ Shows information about the merge request including its files and changes.
|
|||
GET /projects/:id/merge_requests/:merge_request_iid/changes
|
||||
```
|
||||
|
||||
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46190) in GitLab 13.6,
|
||||
diffs associated with the set of changes will have the same size limitations applied as other diffs
|
||||
returned by the API or viewed via the UI. When these limits impact the results, the `overflow`
|
||||
field will contain a value of `true`. Diff data without these limits applied can be retrieved by
|
||||
adding the `access_raw_diffs` parameter, however, it will be slower and more resource-intensive.
|
||||
|
||||
Parameters:
|
||||
|
||||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user.
|
||||
|
|
@ -898,7 +896,7 @@ Parameters:
|
|||
|
||||
## List MR pipelines
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/15454) in GitLab 10.5.0.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/15454) in GitLab 10.5.
|
||||
|
||||
Get a list of merge request pipelines.
|
||||
|
||||
|
|
@ -908,8 +906,8 @@ GET /projects/:id/merge_requests/:merge_request_iid/pipelines
|
|||
|
||||
Parameters:
|
||||
|
||||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
|
||||
- `merge_request_iid` (required) - The internal ID of the merge request
|
||||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user.
|
||||
- `merge_request_iid` (required) - The internal ID of the merge request.
|
||||
|
||||
```json
|
||||
[
|
||||
|
|
@ -926,7 +924,9 @@ Parameters:
|
|||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/31722) in GitLab 12.3.
|
||||
|
||||
Create a new [pipeline for a merge request](../ci/merge_request_pipelines/index.md). A pipeline created via this endpoint will not run a regular branch/tag pipeline, it requires `.gitlab-ci.yml` to be configured with `only: [merge_requests]` to create jobs.
|
||||
Create a new [pipeline for a merge request](../ci/merge_request_pipelines/index.md).
|
||||
A pipeline created via this endpoint doesn't run a regular branch/tag pipeline.
|
||||
It requires `.gitlab-ci.yml` to be configured with `only: [merge_requests]` to create jobs.
|
||||
|
||||
The new pipeline can be:
|
||||
|
||||
|
|
@ -940,8 +940,8 @@ POST /projects/:id/merge_requests/:merge_request_iid/pipelines
|
|||
|
||||
Parameters:
|
||||
|
||||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
|
||||
- `merge_request_iid` (required) - The internal ID of the merge request
|
||||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding).
|
||||
- `merge_request_iid` (required) - The internal ID of the merge request.
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -993,29 +993,19 @@ POST /projects/:id/merge_requests
|
|||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `source_branch` | string | yes | The source branch |
|
||||
| `target_branch` | string | yes | The target branch |
|
||||
| `title` | string | yes | Title of MR |
|
||||
| `assignee_id` | integer | no | Assignee user ID |
|
||||
| `source_branch` | string | yes | The source branch. |
|
||||
| `target_branch` | string | yes | The target branch. |
|
||||
| `title` | string | yes | Title of MR. |
|
||||
| `assignee_id` | integer | no | Assignee user ID. |
|
||||
| `assignee_ids` | integer array | no | The ID of the user(s) to assign the MR to. Set to `0` or provide an empty value to unassign all assignees. |
|
||||
| `description` | string | no | Description of MR. Limited to 1,048,576 characters. |
|
||||
| `target_project_id` | integer | no | The target project (numeric ID) |
|
||||
| `labels` | string | no | Labels for MR as a comma-separated list |
|
||||
| `milestone_id` | integer | no | The global ID of a milestone |
|
||||
| `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging |
|
||||
| `allow_collaboration` | boolean | no | Allow commits from members who can merge to the target branch |
|
||||
| `allow_maintainer_to_push` | boolean | no | Deprecated, see allow_collaboration |
|
||||
| `squash` | boolean | no | Squash commits into a single commit when merging |
|
||||
|
||||
If `approvals_before_merge` **(STARTER)** is not provided, it inherits the value from the
|
||||
target project. If it is provided, then the following conditions must hold in
|
||||
order for it to take effect:
|
||||
|
||||
1. The target project's `approvals_before_merge` must be greater than zero. A
|
||||
value of zero disables approvals for that project.
|
||||
1. The provided value of `approvals_before_merge` must be greater than the
|
||||
target project's `approvals_before_merge`.
|
||||
1. This API returns 201 (created) for a successful response.
|
||||
| `target_project_id` | integer | no | The target project (numeric ID). |
|
||||
| `labels` | string | no | Labels for MR as a comma-separated list. |
|
||||
| `milestone_id` | integer | no | The global ID of a milestone. |
|
||||
| `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging. |
|
||||
| `allow_collaboration` | boolean | no | Allow commits from members who can merge to the target branch. |
|
||||
| `allow_maintainer_to_push` | boolean | no | Deprecated, see `allow_collaboration`. |
|
||||
| `squash` | boolean | no | Squash commits into a single commit when merging. |
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -1128,7 +1118,7 @@ order for it to take effect:
|
|||
}
|
||||
```
|
||||
|
||||
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) will also see
|
||||
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) also see
|
||||
the `approvals_before_merge` parameter:
|
||||
|
||||
```json
|
||||
|
|
@ -1150,10 +1140,10 @@ PUT /projects/:id/merge_requests/:merge_request_iid
|
|||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `merge_request_iid` | integer | yes | The ID of a merge request |
|
||||
| `target_branch` | string | no | The target branch |
|
||||
| `title` | string | no | Title of MR |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
|
||||
| `merge_request_iid` | integer | yes | The ID of a merge request. |
|
||||
| `target_branch` | string | no | The target branch. |
|
||||
| `title` | string | no | Title of MR. |
|
||||
| `assignee_id` | integer | no | The ID of the user to assign the merge request to. Set to `0` or provide an empty value to unassign all assignees. |
|
||||
| `assignee_ids` | integer array | no | The ID of the user(s) to assign the MR to. Set to `0` or provide an empty value to unassign all assignees. |
|
||||
| `milestone_id` | integer | no | The global ID of a milestone to assign the merge request to. Set to `0` or provide an empty value to unassign a milestone.|
|
||||
|
|
@ -1161,12 +1151,12 @@ PUT /projects/:id/merge_requests/:merge_request_iid
|
|||
| `add_labels` | string | no | Comma-separated label names to add to a merge request. |
|
||||
| `remove_labels` | string | no | Comma-separated label names to remove from a merge request. |
|
||||
| `description` | string | no | Description of MR. Limited to 1,048,576 characters. |
|
||||
| `state_event` | string | no | New state (close/reopen) |
|
||||
| `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging |
|
||||
| `squash` | boolean | no | Squash commits into a single commit when merging |
|
||||
| `state_event` | string | no | New state (close/reopen). |
|
||||
| `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging. |
|
||||
| `squash` | boolean | no | Squash commits into a single commit when merging. |
|
||||
| `discussion_locked` | boolean | no | Flag indicating if the merge request's discussion is locked. If the discussion is locked only project members can add, edit or resolve comments. |
|
||||
| `allow_collaboration` | boolean | no | Allow commits from members who can merge to the target branch |
|
||||
| `allow_maintainer_to_push` | boolean | no | Deprecated, see allow_collaboration |
|
||||
| `allow_collaboration` | boolean | no | Allow commits from members who can merge to the target branch. |
|
||||
| `allow_maintainer_to_push` | boolean | no | Deprecated, see `allow_collaboration`. |
|
||||
|
||||
Must include at least one non-required attribute from above.
|
||||
|
||||
|
|
@ -1289,7 +1279,7 @@ Must include at least one non-required attribute from above.
|
|||
}
|
||||
```
|
||||
|
||||
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) will also see
|
||||
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) also see
|
||||
the `approvals_before_merge` parameter:
|
||||
|
||||
```json
|
||||
|
|
@ -1303,7 +1293,7 @@ the `approvals_before_merge` parameter:
|
|||
|
||||
## Delete a merge request
|
||||
|
||||
Only for admins and project owners. Deletes the merge request in question.
|
||||
Only for administrators and project owners. Deletes the merge request in question.
|
||||
|
||||
```plaintext
|
||||
DELETE /projects/:id/merge_requests/:merge_request_iid
|
||||
|
|
@ -1311,8 +1301,8 @@ DELETE /projects/:id/merge_requests/:merge_request_iid
|
|||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request. |
|
||||
|
||||
```shell
|
||||
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/4/merge_requests/85"
|
||||
|
|
@ -1322,13 +1312,13 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://git
|
|||
|
||||
Merge changes submitted with MR using this API.
|
||||
|
||||
If merge request is unable to be accepted (such as Draft, Closed, Pipeline Pending Completion, or Failed while requiring Success) - you'll get a `405` and the error message 'Method Not Allowed'
|
||||
If a merge request is unable to be accepted (such as Draft, Closed, Pipeline Pending Completion, or Failed while requiring Success) - you receive a `405` and the error message 'Method Not Allowed'
|
||||
|
||||
If it has some conflicts and can not be merged - you'll get a `406` and the error message 'Branch cannot be merged'
|
||||
If it has some conflicts and can not be merged - you receive a `406` and the error message 'Branch cannot be merged'
|
||||
|
||||
If the `sha` parameter is passed and does not match the HEAD of the source - you'll get a `409` and the error message 'SHA does not match HEAD of source branch'
|
||||
If the `sha` parameter is passed and does not match the HEAD of the source - you receive a `409` and the error message 'SHA does not match HEAD of source branch'
|
||||
|
||||
If you don't have permissions to accept this merge request - you'll get a `401`
|
||||
If you don't have permissions to accept this merge request - you receive a `401`
|
||||
|
||||
```plaintext
|
||||
PUT /projects/:id/merge_requests/:merge_request_iid/merge
|
||||
|
|
@ -1336,14 +1326,14 @@ PUT /projects/:id/merge_requests/:merge_request_iid/merge
|
|||
|
||||
Parameters:
|
||||
|
||||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
|
||||
- `merge_request_iid` (required) - Internal ID of MR
|
||||
- `merge_commit_message` (optional) - Custom merge commit message
|
||||
- `squash_commit_message` (optional) - Custom squash commit message
|
||||
- `squash` (optional) - if `true` the commits will be squashed into a single commit on merge
|
||||
- `should_remove_source_branch` (optional) - if `true` removes the source branch
|
||||
- `merge_when_pipeline_succeeds` (optional) - if `true` the MR is merged when the pipeline succeeds
|
||||
- `sha` (optional) - if present, then this SHA must match the HEAD of the source branch, otherwise the merge will fail
|
||||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user.
|
||||
- `merge_request_iid` (required) - Internal ID of MR.
|
||||
- `merge_commit_message` (optional) - Custom merge commit message.
|
||||
- `squash_commit_message` (optional) - Custom squash commit message.
|
||||
- `squash` (optional) - if `true` the commits are squashed into a single commit on merge.
|
||||
- `should_remove_source_branch` (optional) - if `true` removes the source branch.
|
||||
- `merge_when_pipeline_succeeds` (optional) - if `true` the MR is merged when the pipeline succeeds.
|
||||
- `sha` (optional) - if present, then this SHA must match the HEAD of the source branch, otherwise the merge fails.
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -1464,7 +1454,7 @@ Parameters:
|
|||
}
|
||||
```
|
||||
|
||||
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) will also see
|
||||
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) also see
|
||||
the `approvals_before_merge` parameter:
|
||||
|
||||
```json
|
||||
|
|
@ -1479,15 +1469,15 @@ the `approvals_before_merge` parameter:
|
|||
## Merge to default merge ref path
|
||||
|
||||
Merge the changes between the merge request source and target branches into `refs/merge-requests/:iid/merge`
|
||||
ref, of the target project repository, if possible. This ref will have the state the target branch would have if
|
||||
ref, of the target project repository, if possible. This ref has the state the target branch would have if
|
||||
a regular merge action was taken.
|
||||
|
||||
This is not a regular merge action given it doesn't change the merge request target branch state in any manner.
|
||||
|
||||
This ref (`refs/merge-requests/:iid/merge`) isn't necessarily overwritten when submitting
|
||||
requests to this API, though it'll make sure the ref has the latest possible state.
|
||||
requests to this API, though it makes sure the ref has the latest possible state.
|
||||
|
||||
If the merge request has conflicts, is empty or already merged, you'll get a `400` and a descriptive error message.
|
||||
If the merge request has conflicts, is empty or already merged, you receive a `400` and a descriptive error message.
|
||||
|
||||
It returns the HEAD commit of `refs/merge-requests/:iid/merge` in the response body in case of `200`.
|
||||
|
||||
|
|
@ -1497,8 +1487,8 @@ GET /projects/:id/merge_requests/:merge_request_iid/merge_ref
|
|||
|
||||
Parameters:
|
||||
|
||||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
|
||||
- `merge_request_iid` (required) - Internal ID of MR
|
||||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user.
|
||||
- `merge_request_iid` (required) - Internal ID of MR.
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -1508,11 +1498,9 @@ Parameters:
|
|||
|
||||
## Cancel Merge When Pipeline Succeeds
|
||||
|
||||
If you don't have permissions to accept this merge request - you'll get a `401`
|
||||
|
||||
If the merge request is already merged or closed - you get `405` and error message 'Method Not Allowed'
|
||||
|
||||
In case the merge request is not set to be merged when the pipeline succeeds, you'll also get a `406` error.
|
||||
- If you don't have permissions to accept this merge request - you receive a `HTTP 401 Unauthorized`.
|
||||
- If the merge request is already merged or closed - you receive a `HTTP 405 Method Not Allowed` and the error message 'Method Not Allowed'.
|
||||
- In case the merge request is not set to be merged when the pipeline succeeds, you also receive a `HTTP 406 Not Acceptable` error.
|
||||
|
||||
```plaintext
|
||||
POST /projects/:id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds
|
||||
|
|
@ -1520,8 +1508,8 @@ POST /projects/:id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_
|
|||
|
||||
Parameters:
|
||||
|
||||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
|
||||
- `merge_request_iid` (required) - Internal ID of MR
|
||||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user.
|
||||
- `merge_request_iid` (required) - Internal ID of the merge request.
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -1642,7 +1630,7 @@ Parameters:
|
|||
}
|
||||
```
|
||||
|
||||
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) will also see
|
||||
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) also see
|
||||
the `approvals_before_merge` parameter:
|
||||
|
||||
```json
|
||||
|
|
@ -1660,7 +1648,7 @@ Automatically rebase the `source_branch` of the merge request against its
|
|||
`target_branch`.
|
||||
|
||||
If you don't have permissions to push to the merge request's source branch -
|
||||
you'll get a `403 Forbidden` response.
|
||||
you receive a `403 Forbidden` response.
|
||||
|
||||
```plaintext
|
||||
PUT /projects/:id/merge_requests/:merge_request_iid/rebase
|
||||
|
|
@ -1668,15 +1656,15 @@ PUT /projects/:id/merge_requests/:merge_request_iid/rebase
|
|||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
|
||||
| `skip_ci` | boolean | no | Set to `true` to skip creating a CI pipeline |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request. |
|
||||
| `skip_ci` | boolean | no | Set to `true` to skip creating a CI pipeline. |
|
||||
|
||||
```shell
|
||||
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/76/merge_requests/1/rebase"
|
||||
```
|
||||
|
||||
This is an asynchronous request. The API will return a `202 Accepted` response
|
||||
This is an asynchronous request. The API returns a `HTTP 202 Accepted` response
|
||||
if the request is enqueued successfully, with a response containing:
|
||||
|
||||
```json
|
||||
|
|
@ -1689,7 +1677,7 @@ You can poll the [Get single MR](#get-single-mr) endpoint with the
|
|||
`include_rebase_in_progress` parameter to check the status of the
|
||||
asynchronous request.
|
||||
|
||||
If the rebase operation is ongoing, the response will include the following:
|
||||
If the rebase operation is ongoing, the response includes the following:
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -1698,7 +1686,7 @@ If the rebase operation is ongoing, the response will include the following:
|
|||
}
|
||||
```
|
||||
|
||||
Once the rebase operation has completed successfully, the response will include
|
||||
After the rebase operation has completed successfully, the response includes
|
||||
the following:
|
||||
|
||||
```json
|
||||
|
|
@ -1708,7 +1696,7 @@ the following:
|
|||
}
|
||||
```
|
||||
|
||||
If the rebase operation fails, the response will include the following:
|
||||
If the rebase operation fails, the response includes the following:
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -1721,7 +1709,7 @@ If the rebase operation fails, the response will include the following:
|
|||
|
||||
Comments are done via the [notes](notes.md) resource.
|
||||
|
||||
## List issues that will close on merge
|
||||
## List issues that close on merge
|
||||
|
||||
Get all the issues that would be closed by merging the provided merge request.
|
||||
|
||||
|
|
@ -1731,8 +1719,8 @@ GET /projects/:id/merge_requests/:merge_request_iid/closes_issues
|
|||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request. |
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/76/merge_requests/1/closes_issues"
|
||||
|
|
@ -1785,7 +1773,7 @@ Example response when the GitLab issue tracker is used:
|
|||
]
|
||||
```
|
||||
|
||||
Example response when an external issue tracker (e.g. Jira) is used:
|
||||
Example response when an external issue tracker (for example, Jira) is used:
|
||||
|
||||
```json
|
||||
[
|
||||
|
|
@ -1799,7 +1787,7 @@ Example response when an external issue tracker (e.g. Jira) is used:
|
|||
## Subscribe to a merge request
|
||||
|
||||
Subscribes the authenticated user to a merge request to receive notification. If the user is already subscribed to the merge request, the
|
||||
status code `304` is returned.
|
||||
status code `HTTP 304 Not Modified` is returned.
|
||||
|
||||
```plaintext
|
||||
POST /projects/:id/merge_requests/:merge_request_iid/subscribe
|
||||
|
|
@ -1807,8 +1795,8 @@ POST /projects/:id/merge_requests/:merge_request_iid/subscribe
|
|||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request. |
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/merge_requests/17/subscribe"
|
||||
|
|
@ -1934,7 +1922,7 @@ Example response:
|
|||
}
|
||||
```
|
||||
|
||||
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) will also see
|
||||
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) also see
|
||||
the `approvals_before_merge` parameter:
|
||||
|
||||
```json
|
||||
|
|
@ -1950,7 +1938,7 @@ the `approvals_before_merge` parameter:
|
|||
|
||||
Unsubscribes the authenticated user from a merge request to not receive
|
||||
notifications from that merge request. If the user is
|
||||
not subscribed to the merge request, the status code `304` is returned.
|
||||
not subscribed to the merge request, the status code `HTTP 304 Not Modified` is returned.
|
||||
|
||||
```plaintext
|
||||
POST /projects/:id/merge_requests/:merge_request_iid/unsubscribe
|
||||
|
|
@ -1958,8 +1946,8 @@ POST /projects/:id/merge_requests/:merge_request_iid/unsubscribe
|
|||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request. |
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/merge_requests/17/unsubscribe"
|
||||
|
|
@ -2085,7 +2073,7 @@ Example response:
|
|||
}
|
||||
```
|
||||
|
||||
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) will also see
|
||||
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) also see
|
||||
the `approvals_before_merge` parameter:
|
||||
|
||||
```json
|
||||
|
|
@ -2101,7 +2089,7 @@ the `approvals_before_merge` parameter:
|
|||
|
||||
Manually creates a to-do item for the current user on a merge request.
|
||||
If there already exists a to-do item for the user on that merge request,
|
||||
status code `304` is returned.
|
||||
status code `HTTP 304 Not Modified` is returned.
|
||||
|
||||
```plaintext
|
||||
POST /projects/:id/merge_requests/:merge_request_iid/todo
|
||||
|
|
@ -2109,8 +2097,8 @@ POST /projects/:id/merge_requests/:merge_request_iid/todo
|
|||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request. |
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/merge_requests/27/todo"
|
||||
|
|
@ -2226,8 +2214,8 @@ GET /projects/:id/merge_requests/:merge_request_iid/versions
|
|||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ------- | -------- | --------------------- |
|
||||
| `id` | String | yes | The ID of the project |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
|
||||
| `id` | String | yes | The ID of the project. |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request. |
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/merge_requests/1/versions"
|
||||
|
|
@ -2267,9 +2255,9 @@ GET /projects/:id/merge_requests/:merge_request_iid/versions/:version_id
|
|||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ------- | -------- | --------------------- |
|
||||
| `id` | String | yes | The ID of the project |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
|
||||
| `version_id` | integer | yes | The ID of the merge request diff version |
|
||||
| `id` | String | yes | The ID of the project. |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request. |
|
||||
| `version_id` | integer | yes | The ID of the merge request diff version. |
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/merge_requests/1/versions/1"
|
||||
|
|
@ -2335,9 +2323,9 @@ POST /projects/:id/merge_requests/:merge_request_iid/time_estimate
|
|||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
|
||||
| `duration` | string | yes | The duration in human format. e.g: 3h30m |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request. |
|
||||
| `duration` | string | yes | The duration in human format, such as `3h30m`. |
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/merge_requests/93/time_estimate?duration=3h30m"
|
||||
|
|
@ -2364,8 +2352,8 @@ POST /projects/:id/merge_requests/:merge_request_iid/reset_time_estimate
|
|||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of a project's merge_request |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of a project's merge_request. |
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/merge_requests/93/reset_time_estimate"
|
||||
|
|
@ -2384,7 +2372,7 @@ Example response:
|
|||
|
||||
## Add spent time for a merge request
|
||||
|
||||
Adds spent time for this merge request
|
||||
Adds spent time for this merge request.
|
||||
|
||||
```plaintext
|
||||
POST /projects/:id/merge_requests/:merge_request_iid/add_spent_time
|
||||
|
|
@ -2392,9 +2380,9 @@ POST /projects/:id/merge_requests/:merge_request_iid/add_spent_time
|
|||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
|
||||
| `duration` | string | yes | The duration in human format. e.g: 3h30m |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request. |
|
||||
| `duration` | string | yes | The duration in human format, such as `3h30m` |
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/merge_requests/93/add_spent_time?duration=1h"
|
||||
|
|
@ -2421,8 +2409,8 @@ POST /projects/:id/merge_requests/:merge_request_iid/reset_spent_time
|
|||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of a project's merge_request |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of a project's merge_request. |
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/merge_requests/93/reset_spent_time"
|
||||
|
|
@ -2447,8 +2435,8 @@ GET /projects/:id/merge_requests/:merge_request_iid/time_stats
|
|||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
|
||||
| `merge_request_iid` | integer | yes | The internal ID of the merge request. |
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/merge_requests/93/time_stats"
|
||||
|
|
|
|||
|
|
@ -20,32 +20,32 @@ Examples are available in several forms. As a collection of:
|
|||
|
||||
## CI/CD examples
|
||||
|
||||
The following table lists examples with step-by-step tutorials that are contained in this section.
|
||||
The following table lists examples with step-by-step tutorials that are contained in this section:
|
||||
|
||||
| Use case | Resource |
|
||||
|:------------------------------|:---------------------------------------------------------------------------------------------------------------------------|
|
||||
| Use case | Resource |
|
||||
|:------------------------------|:---------|
|
||||
| Browser performance testing | [Browser Performance Testing with the Sitespeed.io container](../../user/project/merge_requests/browser_performance_testing.md). |
|
||||
| Load performance testing | [Load Performance Testing with the k6 container](../../user/project/merge_requests/load_performance_testing.md). |
|
||||
| Clojure | [Test a Clojure application with GitLab CI/CD](test-clojure-application.md). |
|
||||
| Deployment with Dpl | [Using `dpl` as deployment tool](deployment/README.md). |
|
||||
| Elixir | [Testing a Phoenix application with GitLab CI/CD](test_phoenix_app_with_gitlab_ci_cd/index.md). |
|
||||
| End-to-end testing | [End-to-end testing with GitLab CI/CD and WebdriverIO](end_to_end_testing_webdriverio/index.md). |
|
||||
| Game development | [DevOps and Game Dev with GitLab CI/CD](devops_and_game_dev_with_gitlab_ci_cd/index.md). |
|
||||
| Clojure | [Test a Clojure application with GitLab CI/CD](test-clojure-application.md). |
|
||||
| Deployment with Dpl | [Using `dpl` as deployment tool](deployment/README.md). |
|
||||
| GitLab Pages | See the [GitLab Pages](../../user/project/pages/index.md) documentation for a complete example of deploying a static site. |
|
||||
| Java with Spring Boot | [Deploy a Spring Boot application to Cloud Foundry with GitLab CI/CD](deploy_spring_boot_to_cloud_foundry/index.md). |
|
||||
| Java with Maven | [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md). |
|
||||
| PHP with PHPunit, atoum | [Testing PHP projects](php.md). |
|
||||
| PHP with NPM, SCP | [Running Composer and NPM scripts with deployment via SCP in GitLab CI/CD](deployment/composer-npm-deploy.md). |
|
||||
| PHP with Laravel, Envoy | [Test and deploy Laravel applications with GitLab CI/CD and Envoy](laravel_with_gitlab_and_envoy/index.md). |
|
||||
| Python on Heroku | [Test and deploy a Python application with GitLab CI/CD](test-and-deploy-python-application-to-heroku.md). |
|
||||
| Ruby on Heroku | [Test and deploy a Ruby application with GitLab CI/CD](test-and-deploy-ruby-application-to-heroku.md). |
|
||||
| Scala on Heroku | [Test and deploy a Scala application to Heroku](test-scala-application.md). |
|
||||
| Elixir | [Testing a Phoenix application with GitLab CI/CD](test_phoenix_app_with_gitlab_ci_cd/index.md). |
|
||||
| End-to-end testing | [End-to-end testing with GitLab CI/CD and WebdriverIO](end_to_end_testing_webdriverio/index.md). |
|
||||
| Game development | [DevOps and Game Dev with GitLab CI/CD](devops_and_game_dev_with_gitlab_ci_cd/index.md). |
|
||||
| Java with Maven | [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md). |
|
||||
| Java with Spring Boot | [Deploy a Spring Boot application to Cloud Foundry with GitLab CI/CD](deploy_spring_boot_to_cloud_foundry/index.md). |
|
||||
| Load performance testing | [Load Performance Testing with the k6 container](../../user/project/merge_requests/load_performance_testing.md). |
|
||||
| Multi project pipeline | [Build, test deploy using multi project pipeline](https://gitlab.com/gitlab-examples/upstream-project). |
|
||||
| NPM with semantic-release | [Publish NPM packages to the GitLab Package Registry using semantic-release](semantic-release.md). |
|
||||
| PHP with Laravel, Envoy | [Test and deploy Laravel applications with GitLab CI/CD and Envoy](laravel_with_gitlab_and_envoy/index.md). |
|
||||
| PHP with NPM, SCP | [Running Composer and NPM scripts with deployment via SCP in GitLab CI/CD](deployment/composer-npm-deploy.md). |
|
||||
| PHP with PHPunit, atoum | [Testing PHP projects](php.md). |
|
||||
| Parallel testing Ruby & JS | [GitLab CI/CD parallel jobs testing for Ruby & JavaScript projects](https://docs.knapsackpro.com/2019/how-to-run-parallel-jobs-for-rspec-tests-on-gitlab-ci-pipeline-and-speed-up-ruby-javascript-testing). |
|
||||
| Secrets management with Vault | [Authenticating and Reading Secrets With Hashicorp Vault](authenticating-with-hashicorp-vault/index.md). |
|
||||
| Multi project pipeline | [Build, test deploy using multi project pipeline](https://gitlab.com/gitlab-examples/upstream-project). |
|
||||
| NPM with semantic-release | [Publish NPM packages to the GitLab Package Registry using semantic-release](semantic-release.md). |
|
||||
| Python on Heroku | [Test and deploy a Python application with GitLab CI/CD](test-and-deploy-python-application-to-heroku.md). |
|
||||
| Ruby on Heroku | [Test and deploy a Ruby application with GitLab CI/CD](test-and-deploy-ruby-application-to-heroku.md). |
|
||||
| Scala on Heroku | [Test and deploy a Scala application to Heroku](test-scala-application.md). |
|
||||
| Secrets management with Vault | [Authenticating and Reading Secrets With Hashicorp Vault](authenticating-with-hashicorp-vault/index.md). |
|
||||
|
||||
### Contributing examples
|
||||
### How to contributing examples
|
||||
|
||||
Contributions are welcome! You can help your favorite programming
|
||||
language users and GitLab by sending a merge request with a guide for that language.
|
||||
|
|
|
|||
|
|
@ -3,9 +3,14 @@ stage: Verify
|
|||
group: Continuous Integration
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
disqus_identifier: 'https://docs.gitlab.com/ee/articles/artifactory_and_gitlab/index.html'
|
||||
author: Fabio Busatto
|
||||
author_gitlab: bikebilly
|
||||
type: tutorial
|
||||
date: 2017-08-15
|
||||
---
|
||||
|
||||
<!-- vale off -->
|
||||
|
||||
# How to deploy Maven projects to Artifactory with GitLab CI/CD
|
||||
|
||||
## Introduction
|
||||
|
|
|
|||
|
|
@ -2,9 +2,15 @@
|
|||
stage: Release
|
||||
group: Release
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
author: Dylan Griffith
|
||||
author_gitlab: DylanGriffith
|
||||
type: tutorial
|
||||
date: 2018-06-07
|
||||
description: "Continuous Deployment of a Spring Boot application to Cloud Foundry with GitLab CI/CD"
|
||||
---
|
||||
|
||||
<!-- vale off -->
|
||||
|
||||
# Deploy a Spring Boot application to Cloud Foundry with GitLab CI/CD
|
||||
|
||||
## Introduction
|
||||
|
|
|
|||
|
|
@ -2,9 +2,14 @@
|
|||
stage: Verify
|
||||
group: Continuous Integration
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
author: Ryan Hall
|
||||
author_gitlab: blitzgren
|
||||
type: tutorial
|
||||
date: 2018-03-07
|
||||
---
|
||||
|
||||
<!-- vale off -->
|
||||
|
||||
# DevOps and Game Dev with GitLab CI/CD
|
||||
|
||||
With advances in WebGL and WebSockets, browsers are extremely viable as game development
|
||||
|
|
|
|||
|
|
@ -2,9 +2,15 @@
|
|||
stage: Verify
|
||||
group: Testing
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
author: Vincent Tunru
|
||||
author_gitlab: Vinnl
|
||||
type: tutorial
|
||||
date: 2019-02-18
|
||||
description: 'Confidence checking your entire app every time a new feature is added can quickly become repetitive. Learn how to automate it with GitLab CI/CD.'
|
||||
---
|
||||
|
||||
<!-- vale off -->
|
||||
|
||||
# End-to-end testing with GitLab CI/CD and WebdriverIO
|
||||
|
||||
[Review Apps](../../review_apps/index.md) are great: for every merge request
|
||||
|
|
|
|||
|
|
@ -2,9 +2,15 @@
|
|||
stage: Verify
|
||||
group: Continuous Integration
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
disqus_identifier: 'https://docs.gitlab.com/ee/articles/laravel_with_gitlab_and_envoy/index.html'
|
||||
author: Mehran Rasulian
|
||||
author_gitlab: mehranrasulian
|
||||
type: tutorial
|
||||
date: 2017-08-31
|
||||
---
|
||||
|
||||
<!-- vale off -->
|
||||
|
||||
# Test and deploy Laravel applications with GitLab CI/CD and Envoy
|
||||
|
||||
## Introduction
|
||||
|
|
|
|||
|
|
@ -2,9 +2,14 @@
|
|||
stage: Verify
|
||||
group: Continuous Integration
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
author: Alexandre S Hostert
|
||||
author_gitlab: Hostert
|
||||
type: tutorial
|
||||
date: 2018-02-20
|
||||
---
|
||||
|
||||
<!-- vale off -->
|
||||
|
||||
# Testing a Phoenix application with GitLab CI/CD
|
||||
|
||||
[Phoenix](https://www.phoenixframework.org/) is a web development framework written in [Elixir](https://elixir-lang.org), which is a
|
||||
|
|
|
|||
|
|
@ -743,6 +743,8 @@ available in the Admin UI.
|
|||
|
||||
#### Releasing the feature
|
||||
|
||||
1. In `ee/config/feature_flags/development/geo_widget_replication.yml`, set `default_enabled: true`
|
||||
|
||||
1. In `ee/app/replicators/geo/widget_replicator.rb`, delete the `self.replication_enabled_by_default?` method:
|
||||
|
||||
```ruby
|
||||
|
|
@ -770,3 +772,260 @@ available in the Admin UI.
|
|||
description: 'Find widget registries on this Geo node',
|
||||
feature_flag: :geo_widget_replication # REMOVE THIS LINE
|
||||
```
|
||||
|
||||
### Repository Replicator Strategy
|
||||
|
||||
Models that refer to any repository on the disk
|
||||
can be easily supported by Geo with the `Geo::RepositoryReplicatorStrategy` module.
|
||||
|
||||
For example, to add support for files referenced by a `Gizmos` model with a
|
||||
`gizmos` table, you would perform the following steps.
|
||||
|
||||
#### Replication
|
||||
|
||||
1. Include `Gitlab::Geo::ReplicableModel` in the `Gizmo` class, and specify
|
||||
the Replicator class `with_replicator Geo::GizmoReplicator`.
|
||||
|
||||
At this point the `Gizmo` class should look like this:
|
||||
|
||||
```ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Gizmo < ApplicationRecord
|
||||
include ::Gitlab::Geo::ReplicableModel
|
||||
|
||||
with_replicator Geo::GizmoReplicator
|
||||
|
||||
# @param primary_key_in [Range, Gizmo] arg to pass to primary_key_in scope
|
||||
# @return [ActiveRecord::Relation<Gizmo>] everything that should be synced to this node, restricted by primary key
|
||||
def self.replicables_for_current_secondary(primary_key_in)
|
||||
# Should be implemented. The idea of the method is to restrict
|
||||
# the set of synced items depending on synchronization settings
|
||||
end
|
||||
|
||||
# Geo checks this method in FrameworkRepositorySyncService to avoid
|
||||
# snapshotting repositories using object pools
|
||||
def pool_repository
|
||||
nil
|
||||
end
|
||||
...
|
||||
end
|
||||
```
|
||||
|
||||
Pay some attention to method `pool_repository`. Not every repository type uses
|
||||
repository pooling. As Geo prefers to use repository snapshotting, it can lead to data loss.
|
||||
Make sure to overwrite `pool_repository` so it returns nil for repositories that do not
|
||||
have pools.
|
||||
|
||||
If there is a common constraint for records to be available for replication,
|
||||
make sure to also overwrite the `available_replicables` scope.
|
||||
|
||||
1. Create `ee/app/replicators/geo/gizmo_replicator.rb`. Implement the
|
||||
`#repository` method which should return a `<Repository>` instance,
|
||||
and implement the class method `.model` to return the `Gizmo` class:
|
||||
|
||||
```ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Geo
|
||||
class GizmoReplicator < Gitlab::Geo::Replicator
|
||||
include ::Geo::RepositoryReplicatorStrategy
|
||||
|
||||
def self.model
|
||||
::Gizmo
|
||||
end
|
||||
|
||||
def repository
|
||||
model_record.repository
|
||||
end
|
||||
|
||||
def self.git_access_class
|
||||
::Gitlab::GitAccessGizmo
|
||||
end
|
||||
|
||||
# The feature flag follows the format `geo_#{replicable_name}_replication`,
|
||||
# so here it would be `geo_gizmo_replication`
|
||||
def self.replication_enabled_by_default?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
1. Generate the feature flag definition file by running the feature flag command
|
||||
and running through the steps:
|
||||
|
||||
```shell
|
||||
bin/feature-flag --ee geo_gizmo_replication --type development --group 'group::geo'
|
||||
```
|
||||
|
||||
1. Make sure Geo push events are created. Usually it needs some
|
||||
change in the `app/workers/post_receive.rb` file. Example:
|
||||
|
||||
```ruby
|
||||
def replicate_gizmo_changes(gizmo)
|
||||
if ::Gitlab::Geo.primary?
|
||||
gizmo.replicator.handle_after_update if gizmo
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
See `app/workers/post_receive.rb` for more examples.
|
||||
|
||||
1. Make sure the repository removal is also handled. You may need to add something
|
||||
like the following in the destroy service of the repository:
|
||||
|
||||
```ruby
|
||||
gizmo.replicator.handle_after_destroy if gizmo.repository
|
||||
```
|
||||
|
||||
1. Add this replicator class to the method `replicator_classes` in
|
||||
`ee/lib/gitlab/geo.rb`:
|
||||
|
||||
```ruby
|
||||
REPLICATOR_CLASSES = [
|
||||
...
|
||||
::Geo::PackageFileReplicator,
|
||||
::Geo::GizmoReplicator
|
||||
]
|
||||
end
|
||||
```
|
||||
|
||||
1. Create `ee/spec/replicators/geo/gizmo_replicator_spec.rb` and perform
|
||||
the necessary setup to define the `model_record` variable for the shared
|
||||
examples:
|
||||
|
||||
```ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Geo::GizmoReplicator do
|
||||
let(:model_record) { build(:gizmo) }
|
||||
|
||||
include_examples 'a repository replicator'
|
||||
end
|
||||
```
|
||||
|
||||
1. Create the `gizmo_registry` table, with columns ordered according to [our guidelines](../ordering_table_columns.md) so Geo secondaries can track the sync and
|
||||
verification state of each Gizmo. This migration belongs in `ee/db/geo/migrate`:
|
||||
|
||||
```ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CreateGizmoRegistry < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
create_table :gizmo_registry, id: :bigserial, force: :cascade do |t|
|
||||
t.datetime_with_timezone :retry_at
|
||||
t.datetime_with_timezone :last_synced_at
|
||||
t.datetime_with_timezone :created_at, null: false
|
||||
t.bigint :gizmo_id, null: false
|
||||
t.integer :state, default: 0, null: false, limit: 2
|
||||
t.integer :retry_count, default: 0, limit: 2
|
||||
t.text :last_sync_failure
|
||||
t.boolean :force_to_redownload
|
||||
t.boolean :missing_on_primary
|
||||
|
||||
t.index :gizmo_id, name: :index_gizmo_registry_on_gizmo_id, unique: true
|
||||
t.index :retry_at
|
||||
t.index :state
|
||||
end
|
||||
|
||||
add_text_limit :gizmo_registry, :last_sync_failure, 255
|
||||
end
|
||||
|
||||
def down
|
||||
drop_table :gizmo_registry
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
1. Create `ee/app/models/geo/gizmo_registry.rb`:
|
||||
|
||||
```ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Geo::GizmoRegistry < Geo::BaseRegistry
|
||||
include Geo::ReplicableRegistry
|
||||
|
||||
MODEL_CLASS = ::Gizmo
|
||||
MODEL_FOREIGN_KEY = :gizmo_id
|
||||
|
||||
belongs_to :gizmo, class_name: 'Gizmo'
|
||||
end
|
||||
```
|
||||
|
||||
1. Update `REGISTRY_CLASSES` in `ee/app/workers/geo/secondary/registry_consistency_worker.rb`.
|
||||
1. Add `gizmo_registry` to `ActiveSupport::Inflector.inflections` in `config/initializers_before_autoloader/000_inflections.rb`.
|
||||
1. Create `ee/spec/factories/geo/gizmo_registry.rb`:
|
||||
|
||||
```ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :geo_gizmo_registry, class: 'Geo::GizmoRegistry' do
|
||||
gizmo
|
||||
state { Geo::GizmoRegistry.state_value(:pending) }
|
||||
|
||||
trait :synced do
|
||||
state { Geo::GizmoRegistry.state_value(:synced) }
|
||||
last_synced_at { 5.days.ago }
|
||||
end
|
||||
|
||||
trait :failed do
|
||||
state { Geo::GizmoRegistry.state_value(:failed) }
|
||||
last_synced_at { 1.day.ago }
|
||||
retry_count { 2 }
|
||||
last_sync_failure { 'Random error' }
|
||||
end
|
||||
|
||||
trait :started do
|
||||
state { Geo::GizmoRegistry.state_value(:started) }
|
||||
last_synced_at { 1.day.ago }
|
||||
retry_count { 0 }
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
1. Create `ee/spec/models/geo/gizmo_registry_spec.rb`:
|
||||
|
||||
```ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Geo::GizmoRegistry, :geo, type: :model do
|
||||
let_it_be(:registry) { create(:geo_gizmo_registry) }
|
||||
|
||||
specify 'factory is valid' do
|
||||
expect(registry).to be_valid
|
||||
end
|
||||
|
||||
include_examples 'a Geo framework registry'
|
||||
end
|
||||
```
|
||||
|
||||
1. Make sure the newly added repository type can be accessed by a secondary.
|
||||
You may need to make some changes to one of the Git access classes.
|
||||
|
||||
Gizmos should now be replicated by Geo.
|
||||
|
||||
#### Metrics
|
||||
|
||||
You need to make the same changes as for Blob Replicator Strategy.
|
||||
You need to make the same changes for the [metrics as in the Blob Replicator Strategy](#metrics).
|
||||
|
||||
#### GraphQL API
|
||||
|
||||
You need to make the same changes for the GraphQL API [as in the Blob Replicator Strategy](#graphql-api).
|
||||
|
||||
#### Releasing the feature
|
||||
|
||||
You need to make the same changes for [releasing the feature as in the Blob Replicator Strategy](#releasing-the-feature).
|
||||
|
|
|
|||
|
|
@ -167,14 +167,13 @@ original repository if you'd like.
|
|||
|
||||
### Download vs clone
|
||||
|
||||
To create a copy of a remote repository files on your computer, you can either
|
||||
**download** or **clone** it. If you download it, you cannot sync it with the
|
||||
To create a copy of a remote repository's files on your computer, you can either
|
||||
**download** or **clone**. If you download, you cannot sync it with the
|
||||
remote repository on GitLab.
|
||||
|
||||
On the other hand, by cloning a repository, you'll download a copy of its
|
||||
files to your local computer, but preserve the Git connection with the remote
|
||||
repository, so that you can work on the its files on your computer and then
|
||||
upload the changes to GitLab.
|
||||
Cloning a repository is the same as downloading, except it preserves the Git connection
|
||||
with the remote repository. This allows you to modify the files locally and
|
||||
upload the changes to the remote repository on GitLab.
|
||||
|
||||
### Pull and push
|
||||
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ Conan version 1.20.5
|
|||
### Install CMake
|
||||
|
||||
When you develop with C++ and Conan, you can select from many available
|
||||
compilers. This example uses the CMake compiler.
|
||||
compilers. This example uses the CMake build system generator.
|
||||
|
||||
To install CMake:
|
||||
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ use these placeholders in the email:
|
|||
You can customize the email display name. Emails sent from Service Desk have
|
||||
this name in the `From` header. The default display name is `GitLab Support Bot`.
|
||||
|
||||
### Using custom email address **(CORE ONLY)**
|
||||
### Using custom email address
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/2201) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.0.
|
||||
> - It was [deployed behind a feature flag](../feature_flags.md), disabled by default.
|
||||
|
|
|
|||
|
|
@ -16,7 +16,12 @@ module API
|
|||
expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? }
|
||||
expose :commit, using: Entities::Commit, if: ->(_, _) { can_download_code? }
|
||||
expose :upcoming_release?, as: :upcoming_release
|
||||
expose :milestones, using: Entities::MilestoneWithStats, if: -> (release, _) { release.milestones.present? && can_read_milestone? }
|
||||
expose :milestones,
|
||||
using: Entities::MilestoneWithStats,
|
||||
if: -> (release, _) { release.milestones.present? && can_read_milestone? } do |release, _|
|
||||
release.milestones.order_by_dates_and_title
|
||||
end
|
||||
|
||||
expose :commit_path, expose_nil: false
|
||||
expose :tag_path, expose_nil: false
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
|
|||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { trackAlertStatusUpdateOptions } from '~/alert_management/constants';
|
||||
import AlertManagementStatus from '~/alert_management/components/alert_status.vue';
|
||||
import updateAlertStatusMutation from '~/alert_management/graphql/mutations/update_alert_status.mutation.graphql';
|
||||
import updateAlertStatusMutation from '~/graphql_shared/mutations/update_alert_status.mutation.graphql';
|
||||
import Tracking from '~/tracking';
|
||||
import mockAlerts from '../mocks/alerts.json';
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils';
|
|||
import { GlDropdown, GlDropdownItem, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { trackAlertStatusUpdateOptions } from '~/alert_management/constants';
|
||||
import AlertSidebarStatus from '~/alert_management/components/sidebar/sidebar_status.vue';
|
||||
import updateAlertStatusMutation from '~/alert_management/graphql/mutations/update_alert_status.mutation.graphql';
|
||||
import updateAlertStatusMutation from '~/graphql_shared/mutations/update_alert_status.mutation.graphql';
|
||||
import Tracking from '~/tracking';
|
||||
import mockAlerts from '../../mocks/alerts.json';
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ describe('BalsamiqViewer', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should call `renderFile` on request success', done => {
|
||||
it('should call `renderFile` on request success', (done) => {
|
||||
jest.spyOn(axios, 'get').mockReturnValue(requestSuccess);
|
||||
jest.spyOn(bv, 'renderFile').mockImplementation(() => {});
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ describe('BalsamiqViewer', () => {
|
|||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('should not call `renderFile` on request failure', done => {
|
||||
it('should not call `renderFile` on request failure', (done) => {
|
||||
jest.spyOn(axios, 'get').mockReturnValue(Promise.reject());
|
||||
jest.spyOn(bv, 'renderFile').mockImplementation(() => {});
|
||||
|
||||
|
|
@ -95,8 +95,8 @@ describe('BalsamiqViewer', () => {
|
|||
balsamiqViewer.viewer = viewer;
|
||||
|
||||
balsamiqViewer.getPreviews.mockReturnValue(previews);
|
||||
balsamiqViewer.renderPreview.mockImplementation(preview => preview);
|
||||
viewer.appendChild.mockImplementation(containerElement => {
|
||||
balsamiqViewer.renderPreview.mockImplementation((preview) => preview);
|
||||
viewer.appendChild.mockImplementation((containerElement) => {
|
||||
container = containerElement;
|
||||
});
|
||||
|
||||
|
|
@ -177,7 +177,9 @@ describe('BalsamiqViewer', () => {
|
|||
database,
|
||||
};
|
||||
|
||||
jest.spyOn(BalsamiqViewer, 'parsePreview').mockImplementation(preview => preview.toString());
|
||||
jest
|
||||
.spyOn(BalsamiqViewer, 'parsePreview')
|
||||
.mockImplementation((preview) => preview.toString());
|
||||
database.exec.mockReturnValue(thumbnails);
|
||||
|
||||
getPreviews = BalsamiqViewer.prototype.getPreviews.call(balsamiqViewer);
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import createFlash from '~/flash';
|
|||
|
||||
jest.mock('~/flash');
|
||||
|
||||
const TEST_LABELS_PAYLOAD = TEST_LABELS.map(label => ({ ...label, set: true }));
|
||||
const TEST_LABELS_TITLES = TEST_LABELS.map(label => label.title);
|
||||
const TEST_LABELS_PAYLOAD = TEST_LABELS.map((label) => ({ ...label, set: true }));
|
||||
const TEST_LABELS_TITLES = TEST_LABELS.map((label) => label.title);
|
||||
|
||||
describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
|
||||
let wrapper;
|
||||
|
|
@ -44,7 +44,8 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
|
|||
};
|
||||
|
||||
const findLabelsSelect = () => wrapper.find({ ref: 'labelsSelect' });
|
||||
const findLabelsTitles = () => wrapper.findAll(GlLabel).wrappers.map(item => item.props('title'));
|
||||
const findLabelsTitles = () =>
|
||||
wrapper.findAll(GlLabel).wrappers.map((item) => item.props('title'));
|
||||
const findCollapsed = () => wrapper.find('[data-testid="collapsed-content"]');
|
||||
|
||||
it('renders "None" when no labels are selected', () => {
|
||||
|
|
@ -76,7 +77,7 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
|
|||
|
||||
it('commits change to the server', () => {
|
||||
expect(wrapper.vm.setActiveIssueLabels).toHaveBeenCalledWith({
|
||||
addLabelIds: TEST_LABELS.map(label => label.id),
|
||||
addLabelIds: TEST_LABELS.map((label) => label.id),
|
||||
projectPath: 'gitlab-org/test-subgroup/gitlab-test',
|
||||
removeLabelIds: [],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -237,19 +237,22 @@ describe('Clusters Store', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe.each(APPLICATION_INSTALLED_STATUSES)('given the current app status is %s', status => {
|
||||
it('marks application as installed', () => {
|
||||
const mockResponseData =
|
||||
CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/2/status.json'].data;
|
||||
const runnerAppIndex = 2;
|
||||
describe.each(APPLICATION_INSTALLED_STATUSES)(
|
||||
'given the current app status is %s',
|
||||
(status) => {
|
||||
it('marks application as installed', () => {
|
||||
const mockResponseData =
|
||||
CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/2/status.json'].data;
|
||||
const runnerAppIndex = 2;
|
||||
|
||||
mockResponseData.applications[runnerAppIndex].status = status;
|
||||
mockResponseData.applications[runnerAppIndex].status = status;
|
||||
|
||||
store.updateStateFromServer(mockResponseData);
|
||||
store.updateStateFromServer(mockResponseData);
|
||||
|
||||
expect(store.state.applications[RUNNER].installed).toBe(true);
|
||||
});
|
||||
});
|
||||
expect(store.state.applications[RUNNER].installed).toBe(true);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it('sets default hostname for jupyter when ingress has a ip address', () => {
|
||||
const mockResponseData =
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ describe('DiffFileHeader component', () => {
|
|||
diffHasDiscussionsResultMock,
|
||||
diffHasExpandedDiscussionsResultMock,
|
||||
...Object.values(mockStoreConfig.modules.diffs.actions),
|
||||
].forEach(mock => mock.mockReset());
|
||||
].forEach((mock) => mock.mockReset());
|
||||
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
|
@ -80,7 +80,7 @@ describe('DiffFileHeader component', () => {
|
|||
const findCollapseIcon = () => wrapper.find({ ref: 'collapseIcon' });
|
||||
const findEditButton = () => wrapper.find({ ref: 'editButton' });
|
||||
|
||||
const createComponent = props => {
|
||||
const createComponent = (props) => {
|
||||
mockStoreConfig = cloneDeep(defaultMockStoreConfig);
|
||||
const store = new Vuex.Store(mockStoreConfig);
|
||||
|
||||
|
|
@ -219,7 +219,7 @@ describe('DiffFileHeader component', () => {
|
|||
});
|
||||
|
||||
describe('for any file', () => {
|
||||
const otherModes = Object.keys(diffViewerModes).filter(m => m !== 'mode_changed');
|
||||
const otherModes = Object.keys(diffViewerModes).filter((m) => m !== 'mode_changed');
|
||||
|
||||
it('for mode_changed file mode displays mode changes', () => {
|
||||
createComponent({
|
||||
|
|
@ -236,20 +236,23 @@ describe('DiffFileHeader component', () => {
|
|||
expect(findModeChangedLine().text()).toMatch(/old-mode.+new-mode/);
|
||||
});
|
||||
|
||||
it.each(otherModes.map(m => [m]))('for %s file mode does not display mode changes', mode => {
|
||||
createComponent({
|
||||
diffFile: {
|
||||
...diffFile,
|
||||
a_mode: 'old-mode',
|
||||
b_mode: 'new-mode',
|
||||
viewer: {
|
||||
...diffFile.viewer,
|
||||
name: diffViewerModes[mode],
|
||||
it.each(otherModes.map((m) => [m]))(
|
||||
'for %s file mode does not display mode changes',
|
||||
(mode) => {
|
||||
createComponent({
|
||||
diffFile: {
|
||||
...diffFile,
|
||||
a_mode: 'old-mode',
|
||||
b_mode: 'new-mode',
|
||||
viewer: {
|
||||
...diffFile.viewer,
|
||||
name: diffViewerModes[mode],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(findModeChangedLine().exists()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
expect(findModeChangedLine().exists()).toBeFalsy();
|
||||
},
|
||||
);
|
||||
|
||||
it('displays the LFS label for files stored in LFS', () => {
|
||||
createComponent({
|
||||
|
|
|
|||
|
|
@ -26,13 +26,15 @@ describe('HiddenFilesWarning', () => {
|
|||
});
|
||||
|
||||
it('has a correct plain diff URL', () => {
|
||||
const plainDiffLink = wrapper.findAll('a').wrappers.filter(x => x.text() === 'Plain diff')[0];
|
||||
const plainDiffLink = wrapper.findAll('a').wrappers.filter((x) => x.text() === 'Plain diff')[0];
|
||||
|
||||
expect(plainDiffLink.attributes('href')).toBe(propsData.plainDiffPath);
|
||||
});
|
||||
|
||||
it('has a correct email patch URL', () => {
|
||||
const emailPatchLink = wrapper.findAll('a').wrappers.filter(x => x.text() === 'Email patch')[0];
|
||||
const emailPatchLink = wrapper
|
||||
.findAll('a')
|
||||
.wrappers.filter((x) => x.text() === 'Email patch')[0];
|
||||
|
||||
expect(emailPatchLink.attributes('href')).toBe(propsData.emailPatchPath);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ describe('Monitoring Component', () => {
|
|||
};
|
||||
|
||||
const findButtons = () => wrapper.findAll(GlButton);
|
||||
const findButtonsByIcon = icon => findButtons().filter(button => button.props('icon') === icon);
|
||||
const findButtonsByIcon = (icon) =>
|
||||
findButtons().filter((button) => button.props('icon') === icon);
|
||||
|
||||
beforeEach(() => {
|
||||
createWrapper();
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ describe('Bulk import status poller', () => {
|
|||
let clientMock;
|
||||
|
||||
const listQueryCacheCalls = () =>
|
||||
clientMock.readQuery.mock.calls.filter(call => call[0].query === bulkImportSourceGroupsQuery);
|
||||
clientMock.readQuery.mock.calls.filter((call) => call[0].query === bulkImportSourceGroupsQuery);
|
||||
|
||||
beforeEach(() => {
|
||||
clientMock = createMockClient({
|
||||
|
|
@ -142,9 +142,11 @@ describe('Bulk import status poller', () => {
|
|||
clientMock.cache.writeQuery({
|
||||
query: bulkImportSourceGroupsQuery,
|
||||
data: {
|
||||
bulkImportSourceGroups: [STARTED_GROUP_1, NOT_STARTED_GROUP, STARTED_GROUP_2].map(group =>
|
||||
generateFakeEntry(group),
|
||||
),
|
||||
bulkImportSourceGroups: [
|
||||
STARTED_GROUP_1,
|
||||
NOT_STARTED_GROUP,
|
||||
STARTED_GROUP_2,
|
||||
].map((group) => generateFakeEntry(group)),
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -155,9 +157,9 @@ describe('Bulk import status poller', () => {
|
|||
await waitForPromises();
|
||||
const [[doc]] = clientMock.query.mock.calls;
|
||||
const { selections } = doc.query.definitions[0].selectionSet;
|
||||
expect(selections.every(field => field.name.value === 'group')).toBeTruthy();
|
||||
expect(selections.every((field) => field.name.value === 'group')).toBeTruthy();
|
||||
expect(selections).toHaveLength(2);
|
||||
expect(selections.map(sel => sel.arguments[0].value.value)).toStrictEqual([
|
||||
expect(selections.map((sel) => sel.arguments[0].value.value)).toStrictEqual([
|
||||
`${TARGET_NAMESPACE}/${STARTED_GROUP_1.import_target.new_name}`,
|
||||
`${TARGET_NAMESPACE}/${STARTED_GROUP_2.import_target.new_name}`,
|
||||
]);
|
||||
|
|
@ -167,7 +169,7 @@ describe('Bulk import status poller', () => {
|
|||
clientMock.cache.writeQuery({
|
||||
query: bulkImportSourceGroupsQuery,
|
||||
data: {
|
||||
bulkImportSourceGroups: [STARTED_GROUP_1, STARTED_GROUP_2].map(group =>
|
||||
bulkImportSourceGroups: [STARTED_GROUP_1, STARTED_GROUP_2].map((group) =>
|
||||
generateFakeEntry(group),
|
||||
),
|
||||
},
|
||||
|
|
@ -189,7 +191,7 @@ describe('Bulk import status poller', () => {
|
|||
clientMock.cache.writeQuery({
|
||||
query: bulkImportSourceGroupsQuery,
|
||||
data: {
|
||||
bulkImportSourceGroups: [STARTED_GROUP_1, STARTED_GROUP_2].map(group =>
|
||||
bulkImportSourceGroups: [STARTED_GROUP_1, STARTED_GROUP_2].map((group) =>
|
||||
generateFakeEntry(group),
|
||||
),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -22,13 +22,14 @@ const issuable2 = {
|
|||
|
||||
const pathIdSeparator = PathIdSeparator.Issue;
|
||||
|
||||
const findFormInput = wrapper => wrapper.find('.js-add-issuable-form-input').element;
|
||||
const findFormInput = (wrapper) => wrapper.find('.js-add-issuable-form-input').element;
|
||||
|
||||
const findRadioInput = (inputs, value) => inputs.filter(input => input.element.value === value)[0];
|
||||
const findRadioInput = (inputs, value) =>
|
||||
inputs.filter((input) => input.element.value === value)[0];
|
||||
|
||||
const findRadioInputs = wrapper => wrapper.findAll('[name="linked-issue-type-radio"]');
|
||||
const findRadioInputs = (wrapper) => wrapper.findAll('[name="linked-issue-type-radio"]');
|
||||
|
||||
const constructWrapper = props => {
|
||||
const constructWrapper = (props) => {
|
||||
return shallowMount(AddIssuableForm, {
|
||||
propsData: {
|
||||
inputValue: '',
|
||||
|
|
@ -192,7 +193,7 @@ describe('AddIssuableForm', () => {
|
|||
});
|
||||
|
||||
describe('when the form is submitted', () => {
|
||||
it('emits an event with a "relates_to" link type when the "relates to" radio input selected', done => {
|
||||
it('emits an event with a "relates_to" link type when the "relates to" radio input selected', (done) => {
|
||||
jest.spyOn(wrapper.vm, '$emit').mockImplementation(() => {});
|
||||
|
||||
wrapper.vm.linkedIssueType = linkedIssueTypesMap.RELATES_TO;
|
||||
|
|
@ -207,7 +208,7 @@ describe('AddIssuableForm', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('emits an event with a "blocks" link type when the "blocks" radio input selected', done => {
|
||||
it('emits an event with a "blocks" link type when the "blocks" radio input selected', (done) => {
|
||||
jest.spyOn(wrapper.vm, '$emit').mockImplementation(() => {});
|
||||
|
||||
wrapper.vm.linkedIssueType = linkedIssueTypesMap.BLOCKS;
|
||||
|
|
@ -222,7 +223,7 @@ describe('AddIssuableForm', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('emits an event with a "is_blocked_by" link type when the "is blocked by" radio input selected', done => {
|
||||
it('emits an event with a "is_blocked_by" link type when the "is blocked by" radio input selected', (done) => {
|
||||
jest.spyOn(wrapper.vm, '$emit').mockImplementation(() => {});
|
||||
|
||||
wrapper.vm.linkedIssueType = linkedIssueTypesMap.IS_BLOCKED_BY;
|
||||
|
|
@ -237,7 +238,7 @@ describe('AddIssuableForm', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('shows error message when error is present', done => {
|
||||
it('shows error message when error is present', (done) => {
|
||||
const itemAddFailureMessage = 'Something went wrong while submitting.';
|
||||
wrapper.setProps({
|
||||
hasError: true,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ describe('DiscussionFilter component', () => {
|
|||
|
||||
const filterDiscussion = jest.fn();
|
||||
|
||||
const findFilter = filterType => wrapper.find(`.dropdown-item[data-filter-type="${filterType}"]`);
|
||||
const findFilter = (filterType) =>
|
||||
wrapper.find(`.dropdown-item[data-filter-type="${filterType}"]`);
|
||||
|
||||
const mountComponent = () => {
|
||||
const discussions = [
|
||||
|
|
@ -145,7 +146,7 @@ describe('DiscussionFilter component', () => {
|
|||
window.mrTabs = undefined;
|
||||
});
|
||||
|
||||
it('only renders when discussion tab is active', done => {
|
||||
it('only renders when discussion tab is active', (done) => {
|
||||
eventHub.$emit('MergeRequestTabChange', 'commit');
|
||||
|
||||
wrapper.vm.$nextTick(() => {
|
||||
|
|
@ -160,7 +161,7 @@ describe('DiscussionFilter component', () => {
|
|||
window.location.hash = '';
|
||||
});
|
||||
|
||||
it('updates the filter when the URL links to a note', done => {
|
||||
it('updates the filter when the URL links to a note', (done) => {
|
||||
window.location.hash = `note_${discussionMock.notes[0].id}`;
|
||||
wrapper.vm.currentValue = discussionFiltersMock[2].value;
|
||||
wrapper.vm.handleLocationHash();
|
||||
|
|
@ -171,7 +172,7 @@ describe('DiscussionFilter component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('does not update the filter when the current filter is "Show all activity"', done => {
|
||||
it('does not update the filter when the current filter is "Show all activity"', (done) => {
|
||||
window.location.hash = `note_${discussionMock.notes[0].id}`;
|
||||
wrapper.vm.handleLocationHash();
|
||||
|
||||
|
|
@ -181,7 +182,7 @@ describe('DiscussionFilter component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('only updates filter when the URL links to a note', done => {
|
||||
it('only updates filter when the URL links to a note', (done) => {
|
||||
window.location.hash = `testing123`;
|
||||
wrapper.vm.handleLocationHash();
|
||||
|
||||
|
|
@ -191,7 +192,7 @@ describe('DiscussionFilter component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('fetches discussions when there is a hash', done => {
|
||||
it('fetches discussions when there is a hash', (done) => {
|
||||
window.location.hash = `note_${discussionMock.notes[0].id}`;
|
||||
wrapper.vm.currentValue = discussionFiltersMock[2].value;
|
||||
jest.spyOn(wrapper.vm, 'selectFilter').mockImplementation(() => {});
|
||||
|
|
@ -203,7 +204,7 @@ describe('DiscussionFilter component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('does not fetch discussions when there is no hash', done => {
|
||||
it('does not fetch discussions when there is no hash', (done) => {
|
||||
window.location.hash = '';
|
||||
jest.spyOn(wrapper.vm, 'selectFilter').mockImplementation(() => {});
|
||||
wrapper.vm.handleLocationHash();
|
||||
|
|
|
|||
|
|
@ -25,8 +25,9 @@ describe('CI Lint Results', () => {
|
|||
};
|
||||
|
||||
const findTable = () => wrapper.find(GlTable);
|
||||
const findByTestId = selector => () => wrapper.find(`[data-testid="ci-lint-${selector}"]`);
|
||||
const findAllByTestId = selector => () => wrapper.findAll(`[data-testid="ci-lint-${selector}"]`);
|
||||
const findByTestId = (selector) => () => wrapper.find(`[data-testid="ci-lint-${selector}"]`);
|
||||
const findAllByTestId = (selector) => () =>
|
||||
wrapper.findAll(`[data-testid="ci-lint-${selector}"]`);
|
||||
const findLinkToDoc = () => wrapper.find(GlLink);
|
||||
const findErrors = findByTestId('errors');
|
||||
const findWarnings = findByTestId('warnings');
|
||||
|
|
@ -37,7 +38,7 @@ describe('CI Lint Results', () => {
|
|||
const findBeforeScripts = findAllByTestId('before-script');
|
||||
const findScripts = findAllByTestId('script');
|
||||
const findAfterScripts = findAllByTestId('after-script');
|
||||
const filterEmptyScripts = property => mockJobs.filter(job => job[property].length !== 0);
|
||||
const filterEmptyScripts = (property) => mockJobs.filter((job) => job[property].length !== 0);
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
|
|
|
|||
|
|
@ -185,7 +185,9 @@ describe('ServiceDeskSetting', () => {
|
|||
const expectedTemplates = [''].concat(templates);
|
||||
|
||||
const dropdown = findTemplateDropdown();
|
||||
const dropdownList = Array.from(dropdown.element.children).map(option => option.innerText);
|
||||
const dropdownList = Array.from(dropdown.element.children).map(
|
||||
(option) => option.innerText,
|
||||
);
|
||||
|
||||
expect(dropdown.element.children).toHaveLength(expectedTemplates.length);
|
||||
expect(dropdownList.includes('Bug')).toEqual(true);
|
||||
|
|
|
|||
|
|
@ -41,9 +41,12 @@ describe('registry_header', () => {
|
|||
|
||||
describe('header', () => {
|
||||
it('has a title', () => {
|
||||
mountComponent();
|
||||
mountComponent({ metadataLoading: true });
|
||||
|
||||
expect(findTitleArea().props('title')).toBe(CONTAINER_REGISTRY_TITLE);
|
||||
expect(findTitleArea().props()).toMatchObject({
|
||||
title: CONTAINER_REGISTRY_TITLE,
|
||||
metadataLoading: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('has a commands slot', () => {
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ describe('List Page', () => {
|
|||
expect(findRegistryHeader().exists()).toBe(true);
|
||||
expect(findRegistryHeader().props()).toMatchObject({
|
||||
imagesCount: 2,
|
||||
metadataLoading: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -170,6 +171,12 @@ describe('List Page', () => {
|
|||
|
||||
expect(findCliCommands().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('title has the metadataLoading props set to true', () => {
|
||||
mountComponent();
|
||||
|
||||
expect(findRegistryHeader().props('metadataLoading')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('list is empty', () => {
|
||||
|
|
|
|||
|
|
@ -88,18 +88,6 @@ Object {
|
|||
},
|
||||
],
|
||||
"milestones": Array [
|
||||
Object {
|
||||
"description": "The 12.4 milestone",
|
||||
"id": "gid://gitlab/Milestone/124",
|
||||
"issueStats": Object {
|
||||
"closed": 1,
|
||||
"total": 4,
|
||||
},
|
||||
"stats": undefined,
|
||||
"title": "12.4",
|
||||
"webPath": undefined,
|
||||
"webUrl": "/releases-namespace/releases-project/-/milestones/2",
|
||||
},
|
||||
Object {
|
||||
"description": "The 12.3 milestone",
|
||||
"id": "gid://gitlab/Milestone/123",
|
||||
|
|
@ -112,6 +100,18 @@ Object {
|
|||
"webPath": undefined,
|
||||
"webUrl": "/releases-namespace/releases-project/-/milestones/1",
|
||||
},
|
||||
Object {
|
||||
"description": "The 12.4 milestone",
|
||||
"id": "gid://gitlab/Milestone/124",
|
||||
"issueStats": Object {
|
||||
"closed": 1,
|
||||
"total": 4,
|
||||
},
|
||||
"stats": undefined,
|
||||
"title": "12.4",
|
||||
"webPath": undefined,
|
||||
"webUrl": "/releases-namespace/releases-project/-/milestones/2",
|
||||
},
|
||||
],
|
||||
"name": "The first release",
|
||||
"releasedAt": "2018-12-10T00:00:00Z",
|
||||
|
|
@ -216,18 +216,6 @@ Object {
|
|||
},
|
||||
],
|
||||
"milestones": Array [
|
||||
Object {
|
||||
"description": "The 12.4 milestone",
|
||||
"id": "gid://gitlab/Milestone/124",
|
||||
"issueStats": Object {
|
||||
"closed": 1,
|
||||
"total": 4,
|
||||
},
|
||||
"stats": undefined,
|
||||
"title": "12.4",
|
||||
"webPath": undefined,
|
||||
"webUrl": "/releases-namespace/releases-project/-/milestones/2",
|
||||
},
|
||||
Object {
|
||||
"description": "The 12.3 milestone",
|
||||
"id": "gid://gitlab/Milestone/123",
|
||||
|
|
@ -240,6 +228,18 @@ Object {
|
|||
"webPath": undefined,
|
||||
"webUrl": "/releases-namespace/releases-project/-/milestones/1",
|
||||
},
|
||||
Object {
|
||||
"description": "The 12.4 milestone",
|
||||
"id": "gid://gitlab/Milestone/124",
|
||||
"issueStats": Object {
|
||||
"closed": 1,
|
||||
"total": 4,
|
||||
},
|
||||
"stats": undefined,
|
||||
"title": "12.4",
|
||||
"webPath": undefined,
|
||||
"webUrl": "/releases-namespace/releases-project/-/milestones/2",
|
||||
},
|
||||
],
|
||||
"name": "The first release",
|
||||
"releasedAt": "2018-12-10T00:00:00Z",
|
||||
|
|
|
|||
|
|
@ -54,17 +54,7 @@ describe('Release block milestone info', () => {
|
|||
});
|
||||
|
||||
it('renders a list of links to all associated milestones', () => {
|
||||
// The API currently returns the milestones in a non-deterministic order,
|
||||
// which causes the frontend fixture used by this test to return the
|
||||
// milestones in one order locally and a different order in the CI pipeline.
|
||||
// This is a bug and is tracked here: https://gitlab.com/gitlab-org/gitlab/-/issues/259012
|
||||
// When this bug is fixed this expectation should be updated to
|
||||
// assert the expected order.
|
||||
const containerText = trimText(milestoneListContainer().text());
|
||||
expect(
|
||||
containerText.includes('Milestones 12.4 • 12.3') ||
|
||||
containerText.includes('Milestones 12.3 • 12.4'),
|
||||
).toBe(true);
|
||||
expect(milestoneListContainer().text()).toMatchInterpolatedText('Milestones 12.3 • 12.4');
|
||||
|
||||
milestones.forEach((m, i) => {
|
||||
const milestoneLink = milestoneListContainer().findAll(GlLink).at(i);
|
||||
|
|
|
|||
|
|
@ -149,9 +149,11 @@ describe('Snippet Edit app', () => {
|
|||
const hasDisabledSubmit = () => Boolean(findSubmitButton().attributes('disabled'));
|
||||
|
||||
const clickSubmitBtn = () => wrapper.find('[data-testid="snippet-edit-form"]').trigger('submit');
|
||||
const triggerBlobActions = actions => findBlobActions().vm.$emit('actions', actions);
|
||||
const setUploadFilesHtml = paths => {
|
||||
wrapper.vm.$el.innerHTML = paths.map(path => `<input name="files[]" value="${path}">`).join('');
|
||||
const triggerBlobActions = (actions) => findBlobActions().vm.$emit('actions', actions);
|
||||
const setUploadFilesHtml = (paths) => {
|
||||
wrapper.vm.$el.innerHTML = paths
|
||||
.map((path) => `<input name="files[]" value="${path}">`)
|
||||
.join('');
|
||||
};
|
||||
const getApiData = ({
|
||||
id,
|
||||
|
|
@ -189,7 +191,7 @@ describe('Snippet Edit app', () => {
|
|||
|
||||
it.each([[{}], [{ snippetGid: '' }]])(
|
||||
'should render all required components with %s',
|
||||
props => {
|
||||
(props) => {
|
||||
createComponent(props);
|
||||
|
||||
expect(wrapper.find(TitleField).exists()).toBe(true);
|
||||
|
|
@ -257,7 +259,7 @@ describe('Snippet Edit app', () => {
|
|||
describe('default visibility', () => {
|
||||
it.each([SNIPPET_VISIBILITY_PRIVATE, SNIPPET_VISIBILITY_INTERNAL, SNIPPET_VISIBILITY_PUBLIC])(
|
||||
'marks %s visibility by default',
|
||||
async visibility => {
|
||||
async (visibility) => {
|
||||
createComponent({
|
||||
props: { snippetGid: '' },
|
||||
selectedLevel: visibility,
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ describe('User Lists Show Mutations', () => {
|
|||
});
|
||||
|
||||
it('adds the new IDs to the state unless empty', () => {
|
||||
newIds.filter(id => id).forEach(id => expect(mockState.userIds).toContain(id));
|
||||
newIds.filter((id) => id).forEach((id) => expect(mockState.userIds).toContain(id));
|
||||
});
|
||||
|
||||
it('does not add duplicate IDs to the state', () => {
|
||||
|
|
@ -80,7 +80,9 @@ describe('User Lists Show Mutations', () => {
|
|||
});
|
||||
|
||||
it('should leave the rest of the IDs alone', () => {
|
||||
userIds.filter(id => id !== removedId).forEach(id => expect(mockState.userIds).toContain(id));
|
||||
userIds
|
||||
.filter((id) => id !== removedId)
|
||||
.forEach((id) => expect(mockState.userIds).toContain(id));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { GlAvatar, GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import { GlAvatar, GlSprintf, GlLink, GlSkeletonLoader } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import component from '~/vue_shared/components/registry/title_area.vue';
|
||||
|
||||
|
|
@ -15,6 +15,7 @@ describe('title area', () => {
|
|||
const findInfoMessages = () => wrapper.findAll('[data-testid="info-message"]');
|
||||
const findDynamicSlot = () => wrapper.find(`[data-testid="${DYNAMIC_SLOT}`);
|
||||
const findSlotOrderElements = () => wrapper.findAll('[slot-test]');
|
||||
const findSkeletonLoader = () => wrapper.find(GlSkeletonLoader);
|
||||
|
||||
const mountComponent = ({ propsData = { title: 'foo' }, slots } = {}) => {
|
||||
wrapper = shallowMount(component, {
|
||||
|
|
@ -100,6 +101,29 @@ describe('title area', () => {
|
|||
expect(findMetadataSlot(name).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('is/are hidden when metadata-loading is true', async () => {
|
||||
mountComponent({ slots: slotMocks, propsData: { title: 'foo', metadataLoading: true } });
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
slotNames.forEach(name => {
|
||||
expect(findMetadataSlot(name).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('metadata skeleton loader', () => {
|
||||
it('is hidden when metadata loading is false', () => {
|
||||
mountComponent();
|
||||
|
||||
expect(findSkeletonLoader().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('is shown when metadata loading is true', () => {
|
||||
mountComponent({ propsData: { metadataLoading: true } });
|
||||
|
||||
expect(findSkeletonLoader().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dynamic slots', () => {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import Vue from 'vue';
|
|||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import stackedProgressBarComponent from '~/vue_shared/components/stacked_progress_bar.vue';
|
||||
|
||||
const createComponent = config => {
|
||||
const createComponent = (config) => {
|
||||
const Component = Vue.extend(stackedProgressBarComponent);
|
||||
const defaultConfig = {
|
||||
successLabel: 'Synced',
|
||||
|
|
@ -29,11 +29,12 @@ describe('StackedProgressBarComponent', () => {
|
|||
vm.$destroy();
|
||||
});
|
||||
|
||||
const findSuccessBarText = wrapper => wrapper.$el.querySelector('.status-green').innerText.trim();
|
||||
const findNeutralBarText = wrapper =>
|
||||
const findSuccessBarText = (wrapper) =>
|
||||
wrapper.$el.querySelector('.status-green').innerText.trim();
|
||||
const findNeutralBarText = (wrapper) =>
|
||||
wrapper.$el.querySelector('.status-neutral').innerText.trim();
|
||||
const findFailureBarText = wrapper => wrapper.$el.querySelector('.status-red').innerText.trim();
|
||||
const findUnavailableBarText = wrapper =>
|
||||
const findFailureBarText = (wrapper) => wrapper.$el.querySelector('.status-red').innerText.trim();
|
||||
const findUnavailableBarText = (wrapper) =>
|
||||
wrapper.$el.querySelector('.status-unavailable').innerText.trim();
|
||||
|
||||
describe('computed', () => {
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ import {
|
|||
findByText,
|
||||
} from '@testing-library/dom';
|
||||
|
||||
const isFolderRowOpen = row => row.matches('.folder.is-open');
|
||||
const isFolderRowOpen = (row) => row.matches('.folder.is-open');
|
||||
|
||||
const getLeftSidebar = () => screen.getByTestId('left-sidebar');
|
||||
|
||||
export const switchLeftSidebarTab = name => {
|
||||
export const switchLeftSidebarTab = (name) => {
|
||||
const sidebar = getLeftSidebar();
|
||||
|
||||
const button = getByLabelText(sidebar, name);
|
||||
|
|
@ -23,7 +23,7 @@ export const switchLeftSidebarTab = name => {
|
|||
export const getStatusBar = () => document.querySelector('.ide-status-bar');
|
||||
|
||||
export const waitForMonacoEditor = () =>
|
||||
new Promise(resolve => window.monaco.editor.onDidCreateEditor(resolve));
|
||||
new Promise((resolve) => window.monaco.editor.onDidCreateEditor(resolve));
|
||||
|
||||
export const findMonacoEditor = () =>
|
||||
screen.findAllByLabelText(/Editor content;/).then(([x]) => x.closest('.monaco-editor'));
|
||||
|
|
@ -31,7 +31,7 @@ export const findMonacoEditor = () =>
|
|||
export const findMonacoDiffEditor = () =>
|
||||
screen.findAllByLabelText(/Editor content;/).then(([x]) => x.closest('.monaco-diff-editor'));
|
||||
|
||||
export const findAndSetEditorValue = async value => {
|
||||
export const findAndSetEditorValue = async (value) => {
|
||||
const editor = await findMonacoEditor();
|
||||
const uri = editor.getAttribute('data-uri');
|
||||
|
||||
|
|
@ -56,10 +56,12 @@ const findFileChild = async (row, name, index = 0) => {
|
|||
const container = await findFileRowContainer(row);
|
||||
const children = await findAllByText(container, name, { selector: '.file-row-name' });
|
||||
|
||||
return children.map(x => x.closest('.file-row')).find(x => x.dataset.level === index.toString());
|
||||
return children
|
||||
.map((x) => x.closest('.file-row'))
|
||||
.find((x) => x.dataset.level === index.toString());
|
||||
};
|
||||
|
||||
const openFileRow = row => {
|
||||
const openFileRow = (row) => {
|
||||
if (!row || isFolderRowOpen(row)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -101,7 +103,7 @@ const fillFileNameModal = async (value, submitText = 'Create file') => {
|
|||
createButton.click();
|
||||
};
|
||||
|
||||
const findAndClickRootAction = async name => {
|
||||
const findAndClickRootAction = async (name) => {
|
||||
const container = await findRootActions();
|
||||
const button = getByLabelText(container, name);
|
||||
|
||||
|
|
@ -112,13 +114,13 @@ export const clickPreviewMarkdown = () => {
|
|||
screen.getByText('Preview Markdown').click();
|
||||
};
|
||||
|
||||
export const openFile = async path => {
|
||||
export const openFile = async (path) => {
|
||||
const row = await findAndTraverseToPath(path);
|
||||
|
||||
openFileRow(row);
|
||||
};
|
||||
|
||||
export const waitForTabToOpen = fileName =>
|
||||
export const waitForTabToOpen = (fileName) =>
|
||||
findByText(document.querySelector('.multi-file-edit-pane'), fileName);
|
||||
|
||||
export const createFile = async (path, content) => {
|
||||
|
|
@ -137,10 +139,10 @@ export const createFile = async (path, content) => {
|
|||
};
|
||||
|
||||
export const getFilesList = () => {
|
||||
return screen.getAllByTestId('file-row-name-container').map(e => e.textContent.trim());
|
||||
return screen.getAllByTestId('file-row-name-container').map((e) => e.textContent.trim());
|
||||
};
|
||||
|
||||
export const deleteFile = async path => {
|
||||
export const deleteFile = async (path) => {
|
||||
const row = await findAndTraverseToPath(path);
|
||||
clickFileRowAction(row, 'Delete');
|
||||
};
|
||||
|
|
@ -152,7 +154,7 @@ export const renameFile = async (path, newPath) => {
|
|||
await fillFileNameModal(newPath, 'Rename file');
|
||||
};
|
||||
|
||||
export const closeFile = async path => {
|
||||
export const closeFile = async (path) => {
|
||||
const button = await screen.getByLabelText(`Close ${path}`, {
|
||||
selector: '.multi-file-tabs button',
|
||||
});
|
||||
|
|
@ -164,7 +166,7 @@ export const commit = async () => {
|
|||
switchLeftSidebarTab('Commit');
|
||||
screen.getByTestId('begin-commit-button').click();
|
||||
|
||||
await screen.findByLabelText(/Commit to .+ branch/).then(x => x.click());
|
||||
await screen.findByLabelText(/Commit to .+ branch/).then((x) => x.click());
|
||||
|
||||
screen.getByText('Commit').click();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -88,12 +88,9 @@ RSpec.describe Mutations::Releases::Create do
|
|||
|
||||
it 'creates the release with the correct milestone associations' do
|
||||
expected_milestone_titles = [milestone_12_3.title, milestone_12_4.title]
|
||||
actual_milestone_titles = new_release.milestones.map { |m| m.title }
|
||||
actual_milestone_titles = new_release.milestones.order_by_dates_and_title.map { |m| m.title }
|
||||
|
||||
# Right now the milestones are returned in a non-deterministic order.
|
||||
# `match_array` should be updated to `eq` once
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/259012 is addressed.
|
||||
expect(actual_milestone_titles).to match_array(expected_milestone_titles)
|
||||
expect(actual_milestone_titles).to eq(expected_milestone_titles)
|
||||
end
|
||||
|
||||
describe 'asset links' do
|
||||
|
|
|
|||
|
|
@ -48,12 +48,7 @@ RSpec.describe Mutations::Releases::Update do
|
|||
expect(updated_release.name).to eq(name) unless except_for == :name
|
||||
expect(updated_release.description).to eq(description) unless except_for == :description
|
||||
expect(updated_release.released_at).to eq(released_at) unless except_for == :released_at
|
||||
|
||||
# Right now the milestones are returned in a non-deterministic order.
|
||||
# Because of this, we need to allow for milestones to be returned in any order.
|
||||
# Once https://gitlab.com/gitlab-org/gitlab/-/issues/259012 has been
|
||||
# fixed, this can be updated to expect a specific order.
|
||||
expect(updated_release.milestones).to match_array([milestone_12_3, milestone_12_4]) unless except_for == :milestones
|
||||
expect(updated_release.milestones.order_by_dates_and_title).to eq([milestone_12_3, milestone_12_4]) unless except_for == :milestones
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Resolvers::ReleaseMilestonesResolver do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:release) { create(:release, :with_milestones, milestones_count: 2) }
|
||||
|
||||
let(:resolved) do
|
||||
resolve(described_class, obj: release)
|
||||
end
|
||||
|
||||
describe '#resolve' do
|
||||
it "returns an OffsetActiveRecordRelationConnection" do
|
||||
expect(resolved).to be_a(::Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection)
|
||||
end
|
||||
|
||||
it "includes the release's milestones in the returned OffsetActiveRecordRelationConnection" do
|
||||
expect(resolved.items).to eq(release.milestones.order_by_dates_and_title)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -3,9 +3,9 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Release do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
let(:release) { create(:release, project: project, author: user) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, :public, :repository) }
|
||||
let_it_be(:release) { create(:release, project: project, author: user) }
|
||||
|
||||
it { expect(release).to be_valid }
|
||||
|
||||
|
|
@ -132,8 +132,10 @@ RSpec.describe Release do
|
|||
end
|
||||
|
||||
describe '#milestone_titles' do
|
||||
let(:release) { create(:release, :with_milestones) }
|
||||
let_it_be(:milestone_1) { create(:milestone, project: project, title: 'Milestone 1') }
|
||||
let_it_be(:milestone_2) { create(:milestone, project: project, title: 'Milestone 2') }
|
||||
let_it_be(:release) { create(:release, project: project, milestones: [milestone_1, milestone_2]) }
|
||||
|
||||
it { expect(release.milestone_titles).to eq(release.milestones.map {|m| m.title }.sort.join(", "))}
|
||||
it { expect(release.milestone_titles).to eq("#{milestone_1.title}, #{milestone_2.title}")}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -116,11 +116,9 @@ RSpec.describe 'Creation of a new release' do
|
|||
context 'when all available mutation arguments are provided' do
|
||||
it_behaves_like 'no errors'
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
it 'returns the new release data' do
|
||||
create_release
|
||||
|
||||
release = mutation_response[:release]
|
||||
expected_direct_asset_url = Gitlab::Routing.url_helpers.project_release_url(project, Release.find_by(tag: tag_name)) << "/downloads#{asset_link[:directAssetPath]}"
|
||||
|
||||
expected_attributes = {
|
||||
|
|
@ -139,21 +137,17 @@ RSpec.describe 'Creation of a new release' do
|
|||
directAssetUrl: expected_direct_asset_url
|
||||
}]
|
||||
}
|
||||
},
|
||||
milestones: {
|
||||
nodes: [
|
||||
{ title: '12.3' },
|
||||
{ title: '12.4' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}.with_indifferent_access
|
||||
|
||||
expect(release).to include(expected_attributes)
|
||||
|
||||
# Right now the milestones are returned in a non-deterministic order.
|
||||
# This `milestones` test should be moved up into the expect(release)
|
||||
# above (and `.to include` updated to `.to eq`) once
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/259012 is addressed.
|
||||
expect(release['milestones']['nodes']).to match_array([
|
||||
{ 'title' => '12.4' },
|
||||
{ 'title' => '12.3' }
|
||||
])
|
||||
expect(mutation_response[:release]).to eq(expected_attributes)
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
||||
context 'when only the required mutation arguments are provided' do
|
||||
|
|
|
|||
|
|
@ -116,15 +116,7 @@ RSpec.describe 'Updating an existing release' do
|
|||
it 'updates the correct field and returns the release' do
|
||||
update_release
|
||||
|
||||
expect(mutation_response[:release]).to include(expected_attributes.merge(updates).except(:milestones))
|
||||
|
||||
# Right now the milestones are returned in a non-deterministic order.
|
||||
# Because of this, we need to test milestones separately to allow
|
||||
# for them to be returned in any order.
|
||||
# Once https://gitlab.com/gitlab-org/gitlab/-/issues/259012 has been
|
||||
# fixed, this special milestone handling can be removed.
|
||||
expected_milestones = expected_attributes.merge(updates)[:milestones]
|
||||
expect(mutation_response[:release][:milestones][:nodes]).to match_array(expected_milestones[:nodes])
|
||||
expect(mutation_response[:release]).to eq(expected_attributes.merge(updates))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -76,11 +76,11 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
|
|||
it 'finds all milestones associated to a release' do
|
||||
post_query
|
||||
|
||||
expected = release.milestones.map do |milestone|
|
||||
expected = release.milestones.order_by_dates_and_title.map do |milestone|
|
||||
{ 'id' => global_id_of(milestone), 'title' => milestone.title }
|
||||
end
|
||||
|
||||
expect(data).to match_array(expected)
|
||||
expect(data).to eq(expected)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -427,4 +427,33 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'milestone order' do
|
||||
let(:path) { path_prefix }
|
||||
let(:current_user) { stranger }
|
||||
let_it_be(:project) { create(:project, :public) }
|
||||
let_it_be_with_reload(:release) { create(:release, project: project) }
|
||||
|
||||
let(:release_fields) do
|
||||
query_graphql_field(%{
|
||||
milestones {
|
||||
nodes {
|
||||
title
|
||||
}
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
let(:actual_milestone_title_order) do
|
||||
post_query
|
||||
|
||||
data.dig('milestones', 'nodes').map { |m| m['title'] }
|
||||
end
|
||||
|
||||
before do
|
||||
release.update!(milestones: [milestone_2, milestone_1])
|
||||
end
|
||||
|
||||
it_behaves_like 'correct release milestone order'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@ RSpec.describe API::Releases do
|
|||
project.add_reporter(reporter)
|
||||
project.add_guest(guest)
|
||||
project.add_developer(developer)
|
||||
|
||||
project.repository.add_tag(maintainer, 'v0.1', commit.id)
|
||||
project.repository.add_tag(maintainer, 'v0.2', commit.id)
|
||||
end
|
||||
|
||||
describe 'GET /projects/:id/releases' do
|
||||
|
|
@ -294,6 +291,25 @@ RSpec.describe API::Releases do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when release is associated to mutiple milestones' do
|
||||
context 'milestones order' do
|
||||
let_it_be(:project) { create(:project, :repository, :public) }
|
||||
let_it_be_with_reload(:release_with_milestones) { create(:release, tag: 'v3.14', project: project) }
|
||||
|
||||
let(:actual_milestone_title_order) do
|
||||
get api("/projects/#{project.id}/releases/#{release_with_milestones.tag}", non_project_member)
|
||||
|
||||
json_response['milestones'].map { |m| m['title'] }
|
||||
end
|
||||
|
||||
before do
|
||||
release_with_milestones.update!(milestones: [milestone_2, milestone_1])
|
||||
end
|
||||
|
||||
it_behaves_like 'correct release milestone order'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when release has link asset' do
|
||||
let!(:link) do
|
||||
create(:release_link,
|
||||
|
|
@ -461,6 +477,10 @@ RSpec.describe API::Releases do
|
|||
}
|
||||
end
|
||||
|
||||
before do
|
||||
initialize_tags
|
||||
end
|
||||
|
||||
it 'accepts the request' do
|
||||
post api("/projects/#{project.id}/releases", maintainer), params: params
|
||||
|
||||
|
|
@ -858,6 +878,10 @@ RSpec.describe API::Releases do
|
|||
description: 'Super nice release')
|
||||
end
|
||||
|
||||
before do
|
||||
initialize_tags
|
||||
end
|
||||
|
||||
it 'accepts the request' do
|
||||
put api("/projects/#{project.id}/releases/v0.1", maintainer), params: params
|
||||
|
||||
|
|
@ -1108,4 +1132,9 @@ RSpec.describe API::Releases do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
def initialize_tags
|
||||
project.repository.add_tag(maintainer, 'v0.1', commit.id)
|
||||
project.repository.add_tag(maintainer, 'v0.2', commit.id)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'correct release milestone order' do
|
||||
let_it_be_with_reload(:milestone_1) { create(:milestone, project: project) }
|
||||
let_it_be_with_reload(:milestone_2) { create(:milestone, project: project) }
|
||||
|
||||
shared_examples 'correct sort order' do
|
||||
it 'sorts milestonee_1 before milestone_2' do
|
||||
freeze_time do
|
||||
expect(actual_milestone_title_order).to eq([milestone_1.title, milestone_2.title])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'due_date' do
|
||||
before do
|
||||
milestone_1.update!(due_date: Time.zone.now, start_date: 1.day.ago, title: 'z')
|
||||
milestone_2.update!(due_date: 1.day.from_now, start_date: 2.days.ago, title: 'a')
|
||||
end
|
||||
|
||||
context 'when both milestones have a due_date' do
|
||||
it_behaves_like 'correct sort order'
|
||||
end
|
||||
|
||||
context 'when one milestone does not have a due_date' do
|
||||
before do
|
||||
milestone_2.update!(due_date: nil)
|
||||
end
|
||||
|
||||
it_behaves_like 'correct sort order'
|
||||
end
|
||||
end
|
||||
|
||||
context 'start_date' do
|
||||
before do
|
||||
milestone_1.update!(due_date: 1.day.from_now, start_date: 1.day.ago, title: 'z' )
|
||||
milestone_2.update!(due_date: 1.day.from_now, start_date: milestone_2_start_date, title: 'a' )
|
||||
end
|
||||
|
||||
context 'when both milestones have a start_date' do
|
||||
let(:milestone_2_start_date) { Time.zone.now }
|
||||
|
||||
it_behaves_like 'correct sort order'
|
||||
end
|
||||
|
||||
context 'when one milestone does not have a start_date' do
|
||||
let(:milestone_2_start_date) { nil }
|
||||
|
||||
it_behaves_like 'correct sort order'
|
||||
end
|
||||
end
|
||||
|
||||
context 'title' do
|
||||
before do
|
||||
milestone_1.update!(due_date: 1.day.from_now, start_date: Time.zone.now, title: 'a' )
|
||||
milestone_2.update!(due_date: 1.day.from_now, start_date: Time.zone.now, title: 'z' )
|
||||
end
|
||||
|
||||
it_behaves_like 'correct sort order'
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue