Skip to content
Snippets Groups Projects
Commit 8b99135b authored by Filipa Lacerda's avatar Filipa Lacerda
Browse files

Merge branch '9-2-stable-ee' into '9-2-stable-fix-conflicts-for-ee-mr-1867'

# Conflicts:
#   app/assets/stylesheets/pages/pipelines.scss
parents 3ef6f0fe 4cb496cb
No related branches found
No related tags found
No related merge requests found
Showing
with 198 additions and 61 deletions
9.2.0-rc4-ee
9.2.0-rc6-ee
Loading
Loading
@@ -42,6 +42,12 @@ gl.issueBoards.BoardSidebar = Vue.extend({
detail: {
handler () {
if (this.issue.id !== this.detail.issue.id) {
$('.block.assignee')
.find('input:not(.js-vue)[name="issue[assignee_ids][]"]')
.each((i, el) => {
$(el).remove();
});
$('.js-issue-board-sidebar', this.$el).each((i, el) => {
$(el).data('glDropdown').clearMenu();
});
Loading
Loading
Loading
Loading
@@ -120,7 +120,7 @@ const DiffNoteAvatars = Vue.extend({
},
methods: {
clickedAvatar(e) {
notes.addDiffNote(e);
notes.onAddDiffNote(e);
 
// Toggle the active state of the toggle all button
this.toggleDiscussionsToggleState();
Loading
Loading
Loading
Loading
@@ -66,4 +66,6 @@ $(() => {
'resolve-count': ResolveCount
},
});
$(window).trigger('resize.nav');
});
Loading
Loading
@@ -4,6 +4,8 @@ import '../vue_shared/vue_resource_interceptor';
 
(() => {
const issueTitleData = document.querySelector('.issue-title-data').dataset;
const initialTitle = document.querySelector('.js-issue-title').innerHTML;
const initialDescription = document.querySelector('.js-issue-description');
const { canUpdateTasksClass, endpoint, isEdited } = issueTitleData;
 
const vm = new Vue({
Loading
Loading
@@ -13,6 +15,8 @@ import '../vue_shared/vue_resource_interceptor';
canUpdateTasksClass,
endpoint,
isEdited,
initialTitle,
initialDescription: initialDescription ? initialDescription.innerHTML : '',
},
}),
});
Loading
Loading
Loading
Loading
@@ -20,6 +20,14 @@ export default {
default: false,
required: false,
},
initialTitle: {
type: String,
required: true,
},
initialDescription: {
type: String,
required: true,
},
},
data() {
const resource = new Service(this.$http, this.endpoint);
Loading
Loading
@@ -39,17 +47,17 @@ export default {
poll,
apiData: {},
tasks: '0 of 0',
title: null,
title: this.initialTitle,
titleText: '',
titleFlag: {
pre: true,
pre: false,
pulse: false,
},
description: null,
description: this.initialDescription,
descriptionText: '',
descriptionChange: false,
descriptionFlag: {
pre: true,
pre: false,
pulse: false,
},
titleEl: document.querySelector('title'),
Loading
Loading
/* eslint-disable no-new, class-methods-use-this */
/* global Breakpoints */
/* global Flash */
/* global notes */
 
import Cookies from 'js-cookie';
import './breakpoints';
Loading
Loading
@@ -251,7 +252,8 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
this.ajaxGet({
url: `${urlPathname}.json${location.search}`,
success: (data) => {
$('#diffs').html(data.html);
const $container = $('#diffs');
$container.html(data.html);
 
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
gl.diffNotesCompileComponents();
Loading
Loading
@@ -278,6 +280,20 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
})
.init();
});
// Scroll any linked note into view
// Similar to `toggler_behavior` in the discussion tab
const hash = window.gl.utils.getLocationHash();
const anchor = hash && $container.find(`[id="${hash}"]`);
if (anchor) {
const notesContent = anchor.closest('.notes_content');
const lineType = notesContent.hasClass('new') ? 'new' : 'old';
notes.addDiffNote(anchor, lineType, false);
anchor[0].scrollIntoView();
// We have multiple elements on the page with `#note_xxx`
// (discussion and diff tabs) and `:target` only applies to the first
anchor.addClass('target');
}
},
});
}
Loading
Loading
Loading
Loading
@@ -31,24 +31,24 @@ const normalizeNewlines = function(str) {
Notes.interval = null;
 
function Notes(notes_url, note_ids, last_fetched_at, view) {
this.updateTargetButtons = bind(this.updateTargetButtons, this);
this.updateComment = bind(this.updateComment, this);
this.visibilityChange = bind(this.visibilityChange, this);
this.cancelDiscussionForm = bind(this.cancelDiscussionForm, this);
this.addDiffNote = bind(this.addDiffNote, this);
this.setupDiscussionNoteForm = bind(this.setupDiscussionNoteForm, this);
this.replyToDiscussionNote = bind(this.replyToDiscussionNote, this);
this.removeNote = bind(this.removeNote, this);
this.cancelEdit = bind(this.cancelEdit, this);
this.updateNote = bind(this.updateNote, this);
this.addDiscussionNote = bind(this.addDiscussionNote, this);
this.addNoteError = bind(this.addNoteError, this);
this.addNote = bind(this.addNote, this);
this.resetMainTargetForm = bind(this.resetMainTargetForm, this);
this.refresh = bind(this.refresh, this);
this.keydownNoteText = bind(this.keydownNoteText, this);
this.toggleCommitList = bind(this.toggleCommitList, this);
this.postComment = bind(this.postComment, this);
this.updateTargetButtons = this.updateTargetButtons.bind(this);
this.updateComment = this.updateComment.bind(this);
this.visibilityChange = this.visibilityChange.bind(this);
this.cancelDiscussionForm = this.cancelDiscussionForm.bind(this);
this.onAddDiffNote = this.onAddDiffNote.bind(this);
this.setupDiscussionNoteForm = this.setupDiscussionNoteForm.bind(this);
this.onReplyToDiscussionNote = this.onReplyToDiscussionNote.bind(this);
this.removeNote = this.removeNote.bind(this);
this.cancelEdit = this.cancelEdit.bind(this);
this.updateNote = this.updateNote.bind(this);
this.addDiscussionNote = this.addDiscussionNote.bind(this);
this.addNoteError = this.addNoteError.bind(this);
this.addNote = this.addNote.bind(this);
this.resetMainTargetForm = this.resetMainTargetForm.bind(this);
this.refresh = this.refresh.bind(this);
this.keydownNoteText = this.keydownNoteText.bind(this);
this.toggleCommitList = this.toggleCommitList.bind(this);
this.postComment = this.postComment.bind(this);
 
this.notes_url = notes_url;
this.note_ids = note_ids;
Loading
Loading
@@ -102,9 +102,9 @@ const normalizeNewlines = function(str) {
// update the file name when an attachment is selected
$(document).on("change", ".js-note-attachment-input", this.updateFormAttachment);
// reply to diff/discussion notes
$(document).on("click", ".js-discussion-reply-button", this.replyToDiscussionNote);
$(document).on("click", ".js-discussion-reply-button", this.onReplyToDiscussionNote);
// add diff note
$(document).on("click", ".js-add-diff-note-button", this.addDiffNote);
$(document).on("click", ".js-add-diff-note-button", this.onAddDiffNote);
// hide diff note form
$(document).on("click", ".js-close-discussion-note-form", this.cancelDiscussionForm);
// toggle commit list
Loading
Loading
@@ -796,10 +796,14 @@ const normalizeNewlines = function(str) {
Shows the note form below the notes.
*/
 
Notes.prototype.replyToDiscussionNote = function(e) {
Notes.prototype.onReplyToDiscussionNote = function(e) {
this.replyToDiscussionNote(e.target);
};
Notes.prototype.replyToDiscussionNote = function(target) {
var form, replyLink;
form = this.cleanForm(this.formClone.clone());
replyLink = $(e.target).closest(".js-discussion-reply-button");
replyLink = $(target).closest(".js-discussion-reply-button");
// insert the form after the button
replyLink
.closest('.discussion-reply-holder')
Loading
Loading
@@ -869,35 +873,43 @@ const normalizeNewlines = function(str) {
Sets up the form and shows it.
*/
 
Notes.prototype.addDiffNote = function(e) {
var $link, addForm, hasNotes, lineType, newForm, nextRow, noteForm, notesContent, notesContentSelector, replyButton, row, rowCssToAdd, targetContent, isDiffCommentAvatar;
Notes.prototype.onAddDiffNote = function(e) {
e.preventDefault();
$link = $(e.currentTarget || e.target);
const $link = $(e.currentTarget || e.target);
const showReplyInput = !$link.hasClass('js-diff-comment-avatar');
this.addDiffNote($link, $link.data('lineType'), showReplyInput);
};
Notes.prototype.addDiffNote = function(target, lineType, showReplyInput) {
var $link, addForm, hasNotes, newForm, noteForm, replyButton, row, rowCssToAdd, targetContent, isDiffCommentAvatar;
$link = $(target);
row = $link.closest("tr");
nextRow = row.next();
hasNotes = nextRow.is(".notes_holder");
const nextRow = row.next();
let targetRow = row;
if (nextRow.is('.notes_holder')) {
targetRow = nextRow;
}
hasNotes = targetRow.is(".notes_holder");
addForm = false;
notesContentSelector = ".notes_content";
let lineTypeSelector = '';
rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\" colspan=\"2\"></td><td class=\"notes_content\"><div class=\"content\"></div></td></tr>";
isDiffCommentAvatar = $link.hasClass('js-diff-comment-avatar');
// In parallel view, look inside the correct left/right pane
if (this.isParallelView()) {
lineType = $link.data("lineType");
notesContentSelector += "." + lineType;
lineTypeSelector = `.${lineType}`;
rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line old\"></td><td class=\"notes_content parallel old\"><div class=\"content\"></div></td><td class=\"notes_line new\"></td><td class=\"notes_content parallel new\"><div class=\"content\"></div></td></tr>";
}
notesContentSelector += " .content";
notesContent = nextRow.find(notesContentSelector);
const notesContentSelector = `.notes_content${lineTypeSelector} .content`;
let notesContent = targetRow.find(notesContentSelector);
 
if (hasNotes && !isDiffCommentAvatar) {
nextRow.show();
notesContent = nextRow.find(notesContentSelector);
if (hasNotes && showReplyInput) {
targetRow.show();
notesContent = targetRow.find(notesContentSelector);
if (notesContent.length) {
notesContent.show();
replyButton = notesContent.find(".js-discussion-reply-button:visible");
if (replyButton.length) {
e.target = replyButton[0];
$.proxy(this.replyToDiscussionNote, replyButton[0], e).call();
this.replyToDiscussionNote(replyButton[0]);
} else {
// In parallel view, the form may not be present in one of the panes
noteForm = notesContent.find(".js-discussion-note-form");
Loading
Loading
@@ -906,18 +918,18 @@ const normalizeNewlines = function(str) {
}
}
}
} else if (!isDiffCommentAvatar) {
} else if (showReplyInput) {
// add a notes row and insert the form
row.after(rowCssToAdd);
nextRow = row.next();
notesContent = nextRow.find(notesContentSelector);
targetRow = row.next();
notesContent = targetRow.find(notesContentSelector);
addForm = true;
} else {
nextRow.show();
targetRow.show();
notesContent.toggle(!notesContent.is(':visible'));
 
if (!nextRow.find('.content:not(:empty)').is(':visible')) {
nextRow.hide();
if (!targetRow.find('.content:not(:empty)').is(':visible')) {
targetRow.hide();
}
}
 
Loading
Loading
@@ -1323,7 +1335,7 @@ const normalizeNewlines = function(str) {
// Show form again on UI on failure
if (isDiscussionForm && $notesContainer.length) {
const replyButton = $notesContainer.parent().find('.js-discussion-reply-button');
$.proxy(this.replyToDiscussionNote, replyButton[0], { target: replyButton[0] }).call();
this.replyToDiscussionNote(replyButton[0]);
$form = $notesContainer.parent().find('form');
}
 
Loading
Loading
Loading
Loading
@@ -64,6 +64,24 @@
capitalizeStageName(name) {
return name.charAt(0).toUpperCase() + name.slice(1);
},
isFirstColumn(index) {
return index === 0;
},
stageConnectorClass(index, stage) {
let className;
// If it's the first stage column and only has one job
if (index === 0 && stage.groups.length === 1) {
className = 'no-margin';
} else if (index > 0) {
// If it is not the first column
className = 'left-margin';
}
return className;
},
},
};
</script>
Loading
Loading
@@ -82,10 +100,12 @@
v-if="!isLoading"
class="stage-column-list">
<stage-column-component
v-for="stage in state.graph"
v-for="(stage, index) in state.graph"
:title="capitalizeStageName(stage.name)"
:jobs="stage.groups"
:key="stage.name"/>
:key="stage.name"
:stage-connector-class="stageConnectorClass(index, stage)"
:is-first-column="isFirstColumn(index)"/>
</ul>
</div>
</div>
Loading
Loading
Loading
Loading
@@ -13,6 +13,18 @@ export default {
type: Array,
required: true,
},
isFirstColumn: {
type: Boolean,
required: false,
default: false,
},
stageConnectorClass: {
type: String,
required: false,
default: '',
},
},
 
components: {
Loading
Loading
@@ -28,20 +40,27 @@ export default {
jobId(job) {
return `ci-badge-${job.name}`;
},
buildConnnectorClass(index) {
return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
},
},
};
</script>
<template>
<li class="stage-column">
<li
class="stage-column"
:class="stageConnectorClass">
<div class="stage-name">
{{title}}
</div>
<div class="builds-container">
<ul>
<li
v-for="job in jobs"
v-for="(job, index) in jobs"
:key="job.id"
class="build"
:class="buildConnnectorClass(index)"
:id="jobId(job)">
 
<div class="curve"></div>
Loading
Loading
Loading
Loading
@@ -187,6 +187,7 @@ export default {
>
<a
class="user-link has-tooltip"
data-container="body"
data-placement="bottom"
:href="assigneeUrl(user)"
:data-title="user.name"
Loading
Loading
$.fx.off = true;
Loading
Loading
@@ -53,7 +53,11 @@ import eventHub from './sidebar/event_hub';
$collapsedSidebar = $block.find('.sidebar-collapsed-user');
$loading = $block.find('.block-loading').fadeOut();
selectedIdDefault = (defaultNullUser && showNullUser) ? 0 : null;
selectedId = $dropdown.data('selected') || selectedIdDefault;
selectedId = $dropdown.data('selected');
if (selectedId === undefined) {
selectedId = selectedIdDefault;
}
 
const assignYourself = function () {
const unassignedSelected = $dropdown.closest('.selectbox')
Loading
Loading
@@ -420,14 +424,24 @@ import eventHub from './sidebar/event_hub';
selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('field-name')) + "']").val();
return assignTo(selected);
}
// Automatically close dropdown after assignee is selected
// since CE has no multiple assignees
// EE does not have a max-select
if ($dropdown.data('max-select') &&
getSelected().length === $dropdown.data('max-select')) {
// Close the dropdown
$dropdown.dropdown('toggle');
}
},
id: function (user) {
return user.id;
},
opened: function(e) {
const $el = $(e.currentTarget);
if ($dropdown.hasClass('js-issue-board-sidebar')) {
selectedId = parseInt($dropdown[0].dataset.selected, 10) || selectedIdDefault;
const selected = getSelected();
if ($dropdown.hasClass('js-issue-board-sidebar') && selected.length === 0) {
this.addInput($dropdown.data('field-name'), 0, {});
}
$el.find('.is-active').removeClass('is-active');
 
Loading
Loading
@@ -435,8 +449,10 @@ import eventHub from './sidebar/event_hub';
$el.find(`li[data-user-id="${id}"] .dropdown-menu-user-link`).addClass('is-active');
}
 
if ($selectbox[0]) {
if (selected.length > 0) {
getSelected().forEach(selectedId => highlightSelected(selectedId));
} else if ($dropdown.hasClass('js-issue-board-sidebar')) {
highlightSelected(0);
} else {
highlightSelected(selectedId);
}
Loading
Loading
@@ -447,15 +463,19 @@ import eventHub from './sidebar/event_hub';
username = user.username ? "@" + user.username : "";
avatar = user.avatar_url ? user.avatar_url : false;
 
let selected = user.id === parseInt(selectedId, 10);
let selected = false;
 
if (this.multiSelect) {
selected = getSelected().find(u => user.id === u);
const fieldName = this.fieldName;
const field = $dropdown.closest('.selectbox').find("input[name='" + fieldName + "'][value='" + user.id + "']");
 
if (field.length) {
selected = true;
}
} else {
selected = user.id === selectedId;
}
 
img = "";
Loading
Loading
export default {
name: 'MRWidgetSHAMismatch',
template: `
<div class="mr-widget-body">
<button
type="button"
class="btn btn-success btn-small"
disabled="true">
Merge
</button>
<span class="bold">
The source branch HEAD has recently changed. Please reload the page and review the changes before merging.
</span>
</div>
`,
};
Loading
Loading
@@ -16,6 +16,7 @@ export { default as NothingToMergeState } from './components/states/mr_widget_no
export { default as MissingBranchState } from './components/states/mr_widget_missing_branch';
export { default as NotAllowedState } from './components/states/mr_widget_not_allowed';
export { default as ReadyToMergeState } from './ee/components/states/mr_widget_ready_to_merge';
export { default as SHAMismatchState } from './components/states/mr_widget_sha_mismatch';
export { default as UnresolvedDiscussionsState } from './components/states/mr_widget_unresolved_discussions';
export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked';
export { default as PipelineFailedState } from './components/states/mr_widget_pipeline_failed';
Loading
Loading
Loading
Loading
@@ -16,6 +16,7 @@ import {
MissingBranchState,
NotAllowedState,
ReadyToMergeState,
SHAMismatchState,
UnresolvedDiscussionsState,
PipelineBlockedState,
PipelineFailedState,
Loading
Loading
@@ -205,6 +206,7 @@ export default {
'mr-widget-not-allowed': NotAllowedState,
'mr-widget-missing-branch': MissingBranchState,
'mr-widget-ready-to-merge': ReadyToMergeState,
'mr-widget-sha-mismatch': SHAMismatchState,
'mr-widget-squash-before-merge': SquashBeforeMerge,
'mr-widget-checking': CheckingState,
'mr-widget-unresolved-discussions': UnresolvedDiscussionsState,
Loading
Loading
Loading
Loading
@@ -21,6 +21,8 @@ export default function deviseState(data) {
return 'unresolvedDiscussions';
} else if (this.isPipelineBlocked) {
return 'pipelineBlocked';
} else if (this.hasSHAChanged) {
return 'shaMismatch';
} else if (this.canBeMerged) {
return 'readyToMerge';
}
Loading
Loading
Loading
Loading
@@ -4,6 +4,7 @@ import { getStateKey } from '../dependencies';
export default class MergeRequestStore {
 
constructor(data) {
this.startingSha = data.diff_head_sha;
this.setData(data);
}
 
Loading
Loading
@@ -70,6 +71,7 @@ export default class MergeRequestStore {
this.canMerge = !!data.merge_path;
this.canCreateIssue = currentUser.can_create_issue || false;
this.canCancelAutomaticMerge = !!data.cancel_merge_when_pipeline_succeeds_path;
this.hasSHAChanged = this.sha !== this.startingSha;
this.canBeMerged = data.can_be_merged || false;
 
// Cherry-pick and Revert actions related
Loading
Loading
Loading
Loading
@@ -16,6 +16,7 @@ const stateToComponentMap = {
mergeWhenPipelineSucceeds: 'mr-widget-merge-when-pipeline-succeeds',
failedToMerge: 'mr-widget-failed-to-merge',
autoMergeFailed: 'mr-widget-auto-merge-failed',
shaMismatch: 'mr-widget-sha-mismatch',
};
 
const statesToShowHelpWidget = [
Loading
Loading
import cancelSVG from 'icons/_icon_action_cancel.svg';
import retrySVG from 'icons/_icon_action_retry.svg';
import playSVG from 'icons/_icon_action_play.svg';
import stopSVG from 'icons/_icon_action_stop.svg';
 
export default function getActionIcon(action) {
let icon;
Loading
Loading
@@ -14,6 +15,9 @@ export default function getActionIcon(action) {
case 'icon_action_play':
icon = playSVG;
break;
case 'icon_action_stop':
icon = stopSVG;
break;
default:
icon = '';
}
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment