Commit e8561287 authored by Alfredo Sumaran's avatar Alfredo Sumaran

Remove IIFEs from diff_notes_bundle.js

parent eeaeb275
...@@ -3,65 +3,63 @@ ...@@ -3,65 +3,63 @@
import Vue from 'vue'; import Vue from 'vue';
(() => { const CommentAndResolveBtn = Vue.extend({
const CommentAndResolveBtn = Vue.extend({ props: {
props: { discussionId: String,
discussionId: String, },
data() {
return {
textareaIsEmpty: true,
discussion: {},
};
},
computed: {
showButton: function () {
if (this.discussion) {
return this.discussion.isResolvable();
} else {
return false;
}
}, },
data() { isDiscussionResolved: function () {
return { return this.discussion.isResolved();
textareaIsEmpty: true,
discussion: {},
};
}, },
computed: { buttonText: function () {
showButton: function () { if (this.isDiscussionResolved) {
if (this.discussion) { if (this.textareaIsEmpty) {
return this.discussion.isResolvable(); return "Unresolve discussion";
} else { } else {
return false; return "Comment & unresolve discussion";
} }
}, } else {
isDiscussionResolved: function () { if (this.textareaIsEmpty) {
return this.discussion.isResolved(); return "Resolve discussion";
},
buttonText: function () {
if (this.isDiscussionResolved) {
if (this.textareaIsEmpty) {
return "Unresolve discussion";
} else {
return "Comment & unresolve discussion";
}
} else { } else {
if (this.textareaIsEmpty) { return "Comment & resolve discussion";
return "Resolve discussion";
} else {
return "Comment & resolve discussion";
}
} }
} }
}, }
created() { },
if (this.discussionId) { created() {
this.discussion = CommentsStore.state[this.discussionId]; if (this.discussionId) {
} this.discussion = CommentsStore.state[this.discussionId];
}, }
mounted: function () { },
if (!this.discussionId) return; mounted: function () {
if (!this.discussionId) return;
const $textarea = $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`); const $textarea = $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`);
this.textareaIsEmpty = $textarea.val() === ''; this.textareaIsEmpty = $textarea.val() === '';
$textarea.on('input.comment-and-resolve-btn', () => { $textarea.on('input.comment-and-resolve-btn', () => {
this.textareaIsEmpty = $textarea.val() === ''; this.textareaIsEmpty = $textarea.val() === '';
}); });
}, },
destroyed: function () { destroyed: function () {
if (!this.discussionId) return; if (!this.discussionId) return;
$(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`).off('input.comment-and-resolve-btn'); $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`).off('input.comment-and-resolve-btn');
} }
}); });
Vue.component('comment-and-resolve-btn', CommentAndResolveBtn); Vue.component('comment-and-resolve-btn', CommentAndResolveBtn);
})(window);
...@@ -4,155 +4,153 @@ ...@@ -4,155 +4,153 @@
import Vue from 'vue'; import Vue from 'vue';
import collapseIcon from '../icons/collapse_icon.svg'; import collapseIcon from '../icons/collapse_icon.svg';
(() => { const DiffNoteAvatars = Vue.extend({
const DiffNoteAvatars = Vue.extend({ props: ['discussionId'],
props: ['discussionId'], data() {
data() { return {
return { isVisible: false,
isVisible: false, lineType: '',
lineType: '', storeState: CommentsStore.state,
storeState: CommentsStore.state, shownAvatars: 3,
shownAvatars: 3, collapseIcon,
collapseIcon, };
}; },
}, template: `
template: ` <div class="diff-comment-avatar-holders"
<div class="diff-comment-avatar-holders" v-show="notesCount !== 0">
v-show="notesCount !== 0"> <div v-if="!isVisible">
<div v-if="!isVisible"> <img v-for="note in notesSubset"
<img v-for="note in notesSubset" class="avatar diff-comment-avatar has-tooltip js-diff-comment-avatar"
class="avatar diff-comment-avatar has-tooltip js-diff-comment-avatar" width="19"
width="19" height="19"
height="19" role="button"
role="button" data-container="body"
data-container="body" data-placement="top"
data-placement="top" data-html="true"
data-html="true" :data-line-type="lineType"
:data-line-type="lineType" :title="note.authorName + ': ' + note.noteTruncated"
:title="note.authorName + ': ' + note.noteTruncated" :src="note.authorAvatar"
:src="note.authorAvatar" @click="clickedAvatar($event)" />
@click="clickedAvatar($event)" /> <span v-if="notesCount > shownAvatars"
<span v-if="notesCount > shownAvatars" class="diff-comments-more-count has-tooltip js-diff-comment-avatar"
class="diff-comments-more-count has-tooltip js-diff-comment-avatar" data-container="body"
data-container="body" data-placement="top"
data-placement="top" ref="extraComments"
ref="extraComments" role="button"
role="button"
:data-line-type="lineType"
:title="extraNotesTitle"
@click="clickedAvatar($event)">{{ moreText }}</span>
</div>
<button class="diff-notes-collapse js-diff-comment-avatar"
type="button"
aria-label="Show comments"
:data-line-type="lineType" :data-line-type="lineType"
@click="clickedAvatar($event)" :title="extraNotesTitle"
v-if="isVisible" @click="clickedAvatar($event)">{{ moreText }}</span>
v-html="collapseIcon">
</button>
</div> </div>
`, <button class="diff-notes-collapse js-diff-comment-avatar"
mounted() { type="button"
aria-label="Show comments"
:data-line-type="lineType"
@click="clickedAvatar($event)"
v-if="isVisible"
v-html="collapseIcon">
</button>
</div>
`,
mounted() {
this.$nextTick(() => {
this.addNoCommentClass();
this.setDiscussionVisible();
this.lineType = $(this.$el).closest('.diff-line-num').hasClass('old_line') ? 'old' : 'new';
});
$(document).on('toggle.comments', () => {
this.$nextTick(() => { this.$nextTick(() => {
this.addNoCommentClass();
this.setDiscussionVisible(); this.setDiscussionVisible();
this.lineType = $(this.$el).closest('.diff-line-num').hasClass('old_line') ? 'old' : 'new';
}); });
});
$(document).on('toggle.comments', () => { },
destroyed() {
$(document).off('toggle.comments');
},
watch: {
storeState: {
handler() {
this.$nextTick(() => { this.$nextTick(() => {
this.setDiscussionVisible(); $('.has-tooltip', this.$el).tooltip('fixTitle');
// We need to add/remove a class to an element that is outside the Vue instance
this.addNoCommentClass();
}); });
});
},
destroyed() {
$(document).off('toggle.comments');
},
watch: {
storeState: {
handler() {
this.$nextTick(() => {
$('.has-tooltip', this.$el).tooltip('fixTitle');
// We need to add/remove a class to an element that is outside the Vue instance
this.addNoCommentClass();
});
},
deep: true,
}, },
deep: true,
}, },
computed: { },
notesSubset() { computed: {
let notes = []; notesSubset() {
let notes = [];
if (this.discussion) {
notes = Object.keys(this.discussion.notes) if (this.discussion) {
.slice(0, this.shownAvatars) notes = Object.keys(this.discussion.notes)
.map(noteId => this.discussion.notes[noteId]); .slice(0, this.shownAvatars)
} .map(noteId => this.discussion.notes[noteId]);
}
return notes;
}, return notes;
extraNotesTitle() { },
if (this.discussion) { extraNotesTitle() {
const extra = this.discussion.notesCount() - this.shownAvatars; if (this.discussion) {
const extra = this.discussion.notesCount() - this.shownAvatars;
return `${extra} more comment${extra > 1 ? 's' : ''}`; return `${extra} more comment${extra > 1 ? 's' : ''}`;
} }
return ''; return '';
}, },
discussion() { discussion() {
return this.storeState[this.discussionId]; return this.storeState[this.discussionId];
}, },
notesCount() { notesCount() {
if (this.discussion) { if (this.discussion) {
return this.discussion.notesCount(); return this.discussion.notesCount();
} }
return 0; return 0;
}, },
moreText() { moreText() {
const plusSign = this.notesCount < 100 ? '+' : ''; const plusSign = this.notesCount < 100 ? '+' : '';
return `${plusSign}${this.notesCount - this.shownAvatars}`; return `${plusSign}${this.notesCount - this.shownAvatars}`;
},
}, },
methods: { },
clickedAvatar(e) { methods: {
notes.addDiffNote(e); clickedAvatar(e) {
notes.addDiffNote(e);
// Toggle the active state of the toggle all button // Toggle the active state of the toggle all button
this.toggleDiscussionsToggleState(); this.toggleDiscussionsToggleState();
this.$nextTick(() => { this.$nextTick(() => {
this.setDiscussionVisible(); this.setDiscussionVisible();
$('.has-tooltip', this.$el).tooltip('fixTitle'); $('.has-tooltip', this.$el).tooltip('fixTitle');
$('.has-tooltip', this.$el).tooltip('hide'); $('.has-tooltip', this.$el).tooltip('hide');
}); });
}, },
addNoCommentClass() { addNoCommentClass() {
const notesCount = this.notesCount; const notesCount = this.notesCount;
$(this.$el).closest('.js-avatar-container') $(this.$el).closest('.js-avatar-container')
.toggleClass('js-no-comment-btn', notesCount > 0) .toggleClass('js-no-comment-btn', notesCount > 0)
.nextUntil('.js-avatar-container') .nextUntil('.js-avatar-container')
.toggleClass('js-no-comment-btn', notesCount > 0); .toggleClass('js-no-comment-btn', notesCount > 0);
}, },
toggleDiscussionsToggleState() { toggleDiscussionsToggleState() {
const $notesHolders = $(this.$el).closest('.code').find('.notes_holder'); const $notesHolders = $(this.$el).closest('.code').find('.notes_holder');
const $visibleNotesHolders = $notesHolders.filter(':visible'); const $visibleNotesHolders = $notesHolders.filter(':visible');
const $toggleDiffCommentsBtn = $(this.$el).closest('.diff-file').find('.js-toggle-diff-comments'); const $toggleDiffCommentsBtn = $(this.$el).closest('.diff-file').find('.js-toggle-diff-comments');
$toggleDiffCommentsBtn.toggleClass('active', $notesHolders.length === $visibleNotesHolders.length); $toggleDiffCommentsBtn.toggleClass('active', $notesHolders.length === $visibleNotesHolders.length);
}, },
setDiscussionVisible() { setDiscussionVisible() {
this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(':visible'); this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(':visible');
},
}, },
}); },
});
Vue.component('diff-note-avatars', DiffNoteAvatars); Vue.component('diff-note-avatars', DiffNoteAvatars);
})();
...@@ -4,192 +4,190 @@ ...@@ -4,192 +4,190 @@
import Vue from 'vue'; import Vue from 'vue';
(() => { const JumpToDiscussion = Vue.extend({
const JumpToDiscussion = Vue.extend({ mixins: [DiscussionMixins],
mixins: [DiscussionMixins], props: {
props: { discussionId: String
discussionId: String },
data: function () {
return {
discussions: CommentsStore.state,
discussion: {},
};
},
computed: {
allResolved: function () {
return this.unresolvedDiscussionCount === 0;
}, },
data: function () { showButton: function () {
return { if (this.discussionId) {
discussions: CommentsStore.state, if (this.unresolvedDiscussionCount > 1) {
discussion: {}, return true;
};
},
computed: {
allResolved: function () {
return this.unresolvedDiscussionCount === 0;
},
showButton: function () {
if (this.discussionId) {
if (this.unresolvedDiscussionCount > 1) {
return true;
} else {
return this.discussionId !== this.lastResolvedId;
}
} else { } else {
return this.unresolvedDiscussionCount >= 1; return this.discussionId !== this.lastResolvedId;
} }
}, } else {
lastResolvedId: function () { return this.unresolvedDiscussionCount >= 1;
let lastId;
for (const discussionId in this.discussions) {
const discussion = this.discussions[discussionId];
if (!discussion.isResolved()) {
lastId = discussion.id;
}
}
return lastId;
} }
}, },
methods: { lastResolvedId: function () {
jumpToNextUnresolvedDiscussion: function () { let lastId;
let discussionsSelector; for (const discussionId in this.discussions) {
let discussionIdsInScope; const discussion = this.discussions[discussionId];
let firstUnresolvedDiscussionId;
let nextUnresolvedDiscussionId;
let activeTab = window.mrTabs.currentAction;
let hasDiscussionsToJumpTo = true;
let jumpToFirstDiscussion = !this.discussionId;
const discussionIdsForElements = function(elements) {
return elements.map(function() {
return $(this).attr('data-discussion-id');
}).toArray();
};
const discussions = this.discussions;
if (activeTab === 'diffs') {
discussionsSelector = '.diffs .notes[data-discussion-id]';
discussionIdsInScope = discussionIdsForElements($(discussionsSelector));
let unresolvedDiscussionCount = 0;
for (let i = 0; i < discussionIdsInScope.length; i += 1) {
const discussionId = discussionIdsInScope[i];
const discussion = discussions[discussionId];
if (discussion && !discussion.isResolved()) {
unresolvedDiscussionCount += 1;
}
}
if (this.discussionId && !this.discussion.isResolved()) { if (!discussion.isResolved()) {
// If this is the last unresolved discussion on the diffs tab, lastId = discussion.id;
// there are no discussions to jump to.
if (unresolvedDiscussionCount === 1) {
hasDiscussionsToJumpTo = false;
}
} else {
// If there are no unresolved discussions on the diffs tab at all,
// there are no discussions to jump to.
if (unresolvedDiscussionCount === 0) {
hasDiscussionsToJumpTo = false;
}
}
} else if (activeTab !== 'notes') {
// If we are on the commits or builds tabs,
// there are no discussions to jump to.
hasDiscussionsToJumpTo = false;
} }
}
return lastId;
}
},
methods: {
jumpToNextUnresolvedDiscussion: function () {
let discussionsSelector;
let discussionIdsInScope;
let firstUnresolvedDiscussionId;
let nextUnresolvedDiscussionId;
let activeTab = window.mrTabs.currentAction;
let hasDiscussionsToJumpTo = true;
let jumpToFirstDiscussion = !this.discussionId;
const discussionIdsForElements = function(elements) {
return elements.map(function() {
return $(this).attr('data-discussion-id');
}).toArray();
};
if (!hasDiscussionsToJumpTo) { const discussions = this.discussions;
// If there are no discussions to jump to on the current page,
// switch to the notes tab and jump to the first disucssion there.
window.mrTabs.activateTab('notes');
activeTab = 'notes';
jumpToFirstDiscussion = true;
}
if (activeTab === 'notes') { if (activeTab === 'diffs') {
discussionsSelector = '.discussion[data-discussion-id]'; discussionsSelector = '.diffs .notes[data-discussion-id]';
discussionIdsInScope = discussionIdsForElements($(discussionsSelector)); discussionIdsInScope = discussionIdsForElements($(discussionsSelector));
}
let unresolvedDiscussionCount = 0;
let currentDiscussionFound = false;
for (let i = 0; i < discussionIdsInScope.length; i += 1) { for (let i = 0; i < discussionIdsInScope.length; i += 1) {
const discussionId = discussionIdsInScope[i]; const discussionId = discussionIdsInScope[i];
const discussion = discussions[discussionId]; const discussion = discussions[discussionId];
if (discussion && !discussion.isResolved()) {
unresolvedDiscussionCount += 1;
}
}
if (!discussion) { if (this.discussionId && !this.discussion.isResolved()) {
// Discussions for comments on commits in this MR don't have a resolved status. // If this is the last unresolved discussion on the diffs tab,
continue; // there are no discussions to jump to.
if (unresolvedDiscussionCount === 1) {
hasDiscussionsToJumpTo = false;
}
} else {
// If there are no unresolved discussions on the diffs tab at all,
// there are no discussions to jump to.
if (unresolvedDiscussionCount === 0) {
hasDiscussionsToJumpTo = false;
} }
}
} else if (activeTab !== 'notes') {
// If we are on the commits or builds tabs,
// there are no discussions to jump to.
hasDiscussionsToJumpTo = false;
}
if (!firstUnresolvedDiscussionId && !discussion.isResolved()) { if (!hasDiscussionsToJumpTo) {
firstUnresolvedDiscussionId = discussionId; // If there are no discussions to jump to on the current page,
// switch to the notes tab and jump to the first disucssion there.
window.mrTabs.activateTab('notes');
activeTab = 'notes';
jumpToFirstDiscussion = true;
}
if (jumpToFirstDiscussion) { if (activeTab === 'notes') {
break; discussionsSelector = '.discussion[data-discussion-id]';
} discussionIdsInScope = discussionIdsForElements($(discussionsSelector));
}
let currentDiscussionFound = false;
for (let i = 0; i < discussionIdsInScope.length; i += 1) {
const discussionId = discussionIdsInScope[i];
const discussion = discussions[discussionId];
if (!discussion) {
// Discussions for comments on commits in this MR don't have a resolved status.
continue;
}
if (!firstUnresolvedDiscussionId && !discussion.isResolved()) {
firstUnresolvedDiscussionId = discussionId;
if (jumpToFirstDiscussion) {
break;
} }
}
if (!jumpToFirstDiscussion) { if (!jumpToFirstDiscussion) {
if (currentDiscussionFound) { if (currentDiscussionFound) {
if (!discussion.isResolved()) { if (!discussion.isResolved()) {
nextUnresolvedDiscussionId = discussionId; nextUnresolvedDiscussionId = discussionId;
break; break;
}
else {
continue;
}
} }
else {
if (discussionId === this.discussionId) { continue;
currentDiscussionFound = true;
} }
} }
if (discussionId === this.discussionId) {
currentDiscussionFound = true;
}
} }
}
nextUnresolvedDiscussionId = nextUnresolvedDiscussionId || firstUnresolvedDiscussionId; nextUnresolvedDiscussionId = nextUnresolvedDiscussionId || firstUnresolvedDiscussionId;
if (!nextUnresolvedDiscussionId) { if (!nextUnresolvedDiscussionId) {
return; return;
} }
let $target = $(`${discussionsSelector}[data-discussion-id="${nextUnresolvedDiscussionId}"]`); let $target = $(`${discussionsSelector}[data-discussion-id="${nextUnresolvedDiscussionId}"]`);
if (activeTab === 'notes') { if (activeTab === 'notes') {
$target = $target.closest('.note-discussion'); $target = $target.closest('.note-discussion');
// If the next discussion is closed, toggle it open. // If the next discussion is closed, toggle it open.
if ($target.find('.js-toggle-content').is(':hidden')) { if ($target.find('.js-toggle-content').is(':hidden')) {
$target.find('.js-toggle-button i').trigger('click'); $target.find('.js-toggle-button i').trigger('click');
}
} else if (activeTab === 'diffs') {
// Resolved discussions are hidden in the diffs tab by default.
// If they are marked unresolved on the notes tab, they will still be hidden on the diffs tab.
// When jumping between unresolved discussions on the diffs tab, we show them.
$target.closest(".content").show();
$target = $target.closest("tr.notes_holder");
$target.show();
// If we are on the diffs tab, we don't scroll to the discussion itself, but to
// 4 diff lines above it: the line the discussion was in response to + 3 context
let prevEl;
for (let i = 0; i < 4; i += 1) {
prevEl = $target.prev();
// If the discussion doesn't have 4 lines above it, we'll have to do with fewer.
if (!prevEl.hasClass("line_holder")) {
break;
} }
} else if (activeTab === 'diffs') {
// Resolved discussions are hidden in the diffs tab by default.
// If they are marked unresolved on the notes tab, they will still be hidden on the diffs tab.
// When jumping between unresolved discussions on the diffs tab, we show them.
$target.closest(".content").show();
$target = $target.closest("tr.notes_holder");
$target.show();
// If we are on the diffs tab, we don't scroll to the discussion itself, but to
// 4 diff lines above it: the line the discussion was in response to + 3 context
let prevEl;
for (let i = 0; i < 4; i += 1) {
prevEl = $target.prev();
// If the discussion doesn't have 4 lines above it, we'll have to do with fewer.
if (!prevEl.hasClass("line_holder")) {
break;
}
$target = prevEl; $target = prevEl;
}
} }
$.scrollTo($target, {
offset: 0
});
} }
},
created() {
this.discussion = this.discussions[this.discussionId];
},
});
Vue.component('jump-to-discussion', JumpToDiscussion); $.scrollTo($target, {
})(); offset: 0
});
}
},
created() {
this.discussion = this.discussions[this.discussionId];
},
});
Vue.component('jump-to-discussion', JumpToDiscussion);
...@@ -2,29 +2,27 @@ ...@@ -2,29 +2,27 @@
import Vue from 'vue'; import Vue from 'vue';
(() => { const NewIssueForDiscussion = Vue.extend({
const NewIssueForDiscussion = Vue.extend({ props: {
props: { discussionId: {
discussionId: { type: String,
type: String, required: true,
required: true,
},
}, },
data() { },
return { data() {
discussions: CommentsStore.state, return {
}; discussions: CommentsStore.state,
};
},
computed: {
discussion() {
return this.discussions[this.discussionId];
}, },
computed: { showButton() {
discussion() { if (this.discussion) return !this.discussion.isResolved();
return this.discussions[this.discussionId]; return false;
},
showButton() {
if (this.discussion) return !this.discussion.isResolved();
return false;
},
}, },
}); },
});
Vue.component('new-issue-for-discussion-btn', NewIssueForDiscussion); Vue.component('new-issue-for-discussion-btn', NewIssueForDiscussion);
})();
...@@ -5,117 +5,115 @@ ...@@ -5,117 +5,115 @@
import Vue from 'vue'; import Vue from 'vue';
(() => { const ResolveBtn = Vue.extend({
const ResolveBtn = Vue.extend({ props: {
props: { noteId: Number,
noteId: Number, discussionId: String,
discussionId: String, resolved: Boolean,
resolved: Boolean, canResolve: Boolean,
canResolve: Boolean, resolvedBy: String,
resolvedBy: String, authorName: String,
authorName: String, authorAvatar: String,
authorAvatar: String, noteTruncated: String,
noteTruncated: String, },
data: function () {
return {
discussions: CommentsStore.state,
loading: false
};
},
watch: {
'discussions': {
handler: 'updateTooltip',
deep: true
}
},
computed: {
discussion: function () {
return this.discussions[this.discussionId];
}, },
data: function () { note: function () {
return { return this.discussion ? this.discussion.getNote(this.noteId) : {};
discussions: CommentsStore.state,
loading: false
};
}, },
watch: { buttonText: function () {
'discussions': { if (this.isResolved) {
handler: 'updateTooltip', return `Resolved by ${this.resolvedByName}`;
deep: true } else if (this.canResolve) {
return 'Mark as resolved';
} else {
return 'Unable to resolve';
} }
}, },
computed: { isResolved: function () {
discussion: function () { if (this.note) {
return this.discussions[this.discussionId]; return this.note.resolved;
}, } else {
note: function () { return false;
return this.discussion ? this.discussion.getNote(this.noteId) : {}; }
}, },
buttonText: function () { resolvedByName: function () {
if (this.isResolved) { return this.note.resolved_by;
return `Resolved by ${this.resolvedByName}`;
} else if (this.canResolve) {
return 'Mark as resolved';
} else {
return 'Unable to resolve';
}
},
isResolved: function () {
if (this.note) {
return this.note.resolved;
} else {
return false;
}
},
resolvedByName: function () {
return this.note.resolved_by;
},
}, },
methods: { },
updateTooltip: function () { methods: {
this.$nextTick(() => { updateTooltip: function () {
$(this.$refs.button) this.$nextTick(() => {
.tooltip('hide') $(this.$refs.button)
.tooltip('fixTitle'); .tooltip('hide')
}); .tooltip('fixTitle');
}, });
resolve: function () { },
if (!this.canResolve) return; resolve: function () {
if (!this.canResolve) return;
let promise; let promise;
this.loading = true; this.loading = true;
if (this.isResolved) { if (this.isResolved) {
promise = ResolveService promise = ResolveService
.unresolve(this.noteId); .unresolve(this.noteId);
} else { } else {
promise = ResolveService promise = ResolveService
.resolve(this.noteId); .resolve(this.noteId);
} }
promise.then((response) => { promise.then((response) => {
this.loading = false; this.loading = false;
if (response.status === 200) { if (response.status === 200) {
const data = response.json(); const data = response.json();
const resolved_by = data ? data.resolved_by : null; const resolved_by = data ? data.resolved_by : null;
CommentsStore.update(this.discussionId, this.noteId, !this.isResolved, resolved_by); CommentsStore.update(this.discussionId, this.noteId, !this.isResolved, resolved_by);
this.discussion.updateHeadline(data); this.discussion.updateHeadline(data);
} else { } else {
new Flash('An error occurred when trying to resolve a comment. Please try again.', 'alert'); new Flash('An error occurred when trying to resolve a comment. Please try again.', 'alert');
} }
this.updateTooltip(); this.updateTooltip();
});
}
},
mounted: function () {
$(this.$refs.button).tooltip({
container: 'body'
});
},
beforeDestroy: function () {
CommentsStore.delete(this.discussionId, this.noteId);
},
created: function () {
CommentsStore.create({
discussionId: this.discussionId,
noteId: this.noteId,
canResolve: this.canResolve,
resolved: this.resolved,
resolvedBy: this.resolvedBy,
authorName: this.authorName,
authorAvatar: this.authorAvatar,
noteTruncated: this.noteTruncated,
}); });
} }
}); },
mounted: function () {
$(this.$refs.button).tooltip({
container: 'body'
});
},
beforeDestroy: function () {
CommentsStore.delete(this.discussionId, this.noteId);
},
created: function () {
CommentsStore.create({
discussionId: this.discussionId,
noteId: this.noteId,
canResolve: this.canResolve,
resolved: this.resolved,
resolvedBy: this.resolvedBy,
authorName: this.authorName,
authorAvatar: this.authorAvatar,
noteTruncated: this.noteTruncated,
});
}
});
Vue.component('resolve-btn', ResolveBtn); Vue.component('resolve-btn', ResolveBtn);
})();
...@@ -4,24 +4,22 @@ ...@@ -4,24 +4,22 @@
import Vue from 'vue'; import Vue from 'vue';
((w) => { window.ResolveCount = Vue.extend({
w.ResolveCount = Vue.extend({ mixins: [DiscussionMixins],
mixins: [DiscussionMixins], props: {
props: { loggedOut: Boolean
loggedOut: Boolean },
data: function () {
return {
discussions: CommentsStore.state
};
},
computed: {
allResolved: function () {
return this.resolvedDiscussionCount === this.discussionCount;
}, },
data: function () { resolvedCountText() {
return { return this.discussionCount === 1 ? 'discussion' : 'discussions';
discussions: CommentsStore.state
};
},
computed: {
allResolved: function () {
return this.resolvedDiscussionCount === this.discussionCount;
},
resolvedCountText() {
return this.discussionCount === 1 ? 'discussion' : 'discussions';
}
} }
}); }
})(window); });
...@@ -4,59 +4,57 @@ ...@@ -4,59 +4,57 @@
import Vue from 'vue'; import Vue from 'vue';
(() => { const ResolveDiscussionBtn = Vue.extend({
const ResolveDiscussionBtn = Vue.extend({ props: {
props: { discussionId: String,
discussionId: String, mergeRequestId: Number,
mergeRequestId: Number, canResolve: Boolean,
canResolve: Boolean, },
}, data: function() {
data: function() { return {
return { discussion: {},
discussion: {}, };
}; },
computed: {
showButton: function () {
if (this.discussion) {
return this.discussion.isResolvable();
} else {
return false;
}
}, },
computed: { isDiscussionResolved: function () {
showButton: function () { if (this.discussion) {
if (this.discussion) { return this.discussion.isResolved();
return this.discussion.isResolvable(); } else {
} else { return false;
return false;
}
},
isDiscussionResolved: function () {
if (this.discussion) {
return this.discussion.isResolved();
} else {
return false;
}
},
buttonText: function () {
if (this.isDiscussionResolved) {
return "Unresolve discussion";
} else {
return "Resolve discussion";
}
},
loading: function () {
if (this.discussion) {
return this.discussion.loading;
} else {
return false;
}
} }
}, },
methods: { buttonText: function () {
resolve: function () { if (this.isDiscussionResolved) {
ResolveService.toggleResolveForDiscussion(this.mergeRequestId, this.discussionId); return "Unresolve discussion";
} else {
return "Resolve discussion";
} }
}, },
created: function () { loading: function () {
CommentsStore.createDiscussion(this.discussionId, this.canResolve); if (this.discussion) {
return this.discussion.loading;
this.discussion = CommentsStore.state[this.discussionId]; } else {
return false;
}
}
},
methods: {
resolve: function () {
ResolveService.toggleResolveForDiscussion(this.mergeRequestId, this.discussionId);
} }
}); },
created: function () {
CommentsStore.createDiscussion(this.discussionId, this.canResolve);
this.discussion = CommentsStore.state[this.discussionId];
}
});
Vue.component('resolve-discussion-btn', ResolveDiscussionBtn); Vue.component('resolve-discussion-btn', ResolveDiscussionBtn);
})();
/* eslint-disable object-shorthand, func-names, guard-for-in, no-restricted-syntax, comma-dangle, no-param-reassign, max-len */ /* eslint-disable object-shorthand, func-names, guard-for-in, no-restricted-syntax, comma-dangle, no-param-reassign, max-len */
((w) => { window.DiscussionMixins = {
w.DiscussionMixins = { computed: {
computed: { discussionCount: function () {
discussionCount: function () { return Object.keys(this.discussions).length;
return Object.keys(this.discussions).length; },
}, resolvedDiscussionCount: function () {
resolvedDiscussionCount: function () { let resolvedCount = 0;
let resolvedCount = 0;
for (const discussionId in this.discussions) { for (const discussionId in this.discussions) {
const discussion = this.discussions[discussionId]; const discussion = this.discussions[discussionId];
if (discussion.isResolved()) { if (discussion.isResolved()) {
resolvedCount += 1; resolvedCount += 1;
}
} }
}
return resolvedCount; return resolvedCount;
}, },
unresolvedDiscussionCount: function () { unresolvedDiscussionCount: function () {
let unresolvedCount = 0; let unresolvedCount = 0;
for (const discussionId in this.discussions) { for (const discussionId in this.discussions) {
const discussion = this.discussions[discussionId]; const discussion = this.discussions[discussionId];
if (!discussion.isResolved()) { if (!discussion.isResolved()) {
unresolvedCount += 1; unresolvedCount += 1;
}
} }
return unresolvedCount;
} }
return unresolvedCount;
} }
}; }
})(window); };
...@@ -9,76 +9,74 @@ require('../../vue_shared/vue_resource_interceptor'); ...@@ -9,76 +9,74 @@ require('../../vue_shared/vue_resource_interceptor');
Vue.use(VueResource); Vue.use(VueResource);
(() => { window.gl = window.gl || {};
window.gl = window.gl || {};
class ResolveServiceClass { class ResolveServiceClass {
constructor(root) { constructor(root) {
this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve`); this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve`);
this.discussionResource = Vue.resource(`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve`); this.discussionResource = Vue.resource(`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve`);
} }
resolve(noteId) {
return this.noteResource.save({ noteId }, {});
}
unresolve(noteId) { resolve(noteId) {
return this.noteResource.delete({ noteId }, {}); return this.noteResource.save({ noteId }, {});
} }
toggleResolveForDiscussion(mergeRequestId, discussionId) { unresolve(noteId) {
const discussion = CommentsStore.state[discussionId]; return this.noteResource.delete({ noteId }, {});
const isResolved = discussion.isResolved(); }
let promise;
if (isResolved) { toggleResolveForDiscussion(mergeRequestId, discussionId) {
promise = this.unResolveAll(mergeRequestId, discussionId); const discussion = CommentsStore.state[discussionId];
} else { const isResolved = discussion.isResolved();
promise = this.resolveAll(mergeRequestId, discussionId); let promise;
}
promise.then((response) => { if (isResolved) {
discussion.loading = false; promise = this.unResolveAll(mergeRequestId, discussionId);
} else {
promise = this.resolveAll(mergeRequestId, discussionId);
}
if (response.status === 200) { promise.then((response) => {
const data = response.json(); discussion.loading = false;
const resolved_by = data ? data.resolved_by : null;
if (isResolved) { if (response.status === 200) {
discussion.unResolveAllNotes(); const data = response.json();
} else { const resolved_by = data ? data.resolved_by : null;
discussion.resolveAllNotes(resolved_by);
}
discussion.updateHeadline(data); if (isResolved) {
discussion.unResolveAllNotes();
} else { } else {
new Flash('An error occurred when trying to resolve a discussion. Please try again.', 'alert'); discussion.resolveAllNotes(resolved_by);
} }
});
}
resolveAll(mergeRequestId, discussionId) { discussion.updateHeadline(data);
const discussion = CommentsStore.state[discussionId]; } else {
new Flash('An error occurred when trying to resolve a discussion. Please try again.', 'alert');
}
});
}
discussion.loading = true; resolveAll(mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId];
return this.discussionResource.save({ discussion.loading = true;
mergeRequestId,
discussionId return this.discussionResource.save({
}, {}); mergeRequestId,
} discussionId
}, {});
}
unResolveAll(mergeRequestId, discussionId) { unResolveAll(mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId]; const discussion = CommentsStore.state[discussionId];
discussion.loading = true; discussion.loading = true;
return this.discussionResource.delete({ return this.discussionResource.delete({
mergeRequestId, mergeRequestId,
discussionId discussionId
}, {}); }, {});
}
} }
}
gl.DiffNotesResolveServiceClass = ResolveServiceClass; gl.DiffNotesResolveServiceClass = ResolveServiceClass;
})();
...@@ -3,56 +3,54 @@ ...@@ -3,56 +3,54 @@
import Vue from 'vue'; import Vue from 'vue';
((w) => { window.CommentsStore = {
w.CommentsStore = { state: {},
state: {}, get: function (discussionId, noteId) {
get: function (discussionId, noteId) { return this.state[discussionId].getNote(noteId);
return this.state[discussionId].getNote(noteId); },
}, createDiscussion: function (discussionId, canResolve) {
createDiscussion: function (discussionId, canResolve) { let discussion = this.state[discussionId];
let discussion = this.state[discussionId]; if (!this.state[discussionId]) {
if (!this.state[discussionId]) { discussion = new DiscussionModel(discussionId);
discussion = new DiscussionModel(discussionId); Vue.set(this.state, discussionId, discussion);
Vue.set(this.state, discussionId, discussion); }
}
if (canResolve !== undefined) { if (canResolve !== undefined) {
discussion.canResolve = canResolve; discussion.canResolve = canResolve;
} }
return discussion; return discussion;
}, },
create: function (noteObj) { create: function (noteObj) {
const discussion = this.createDiscussion(noteObj.discussionId); const discussion = this.createDiscussion(noteObj.discussionId);
discussion.createNote(noteObj);
},
update: function (discussionId, noteId, resolved, resolved_by) {
const discussion = this.state[discussionId];
const note = discussion.getNote(noteId);
note.resolved = resolved;
note.resolved_by = resolved_by;
},
delete: function (discussionId, noteId) {
const discussion = this.state[discussionId];
discussion.deleteNote(noteId);
if (discussion.notesCount() === 0) {
Vue.delete(this.state, discussionId);
}
},
unresolvedDiscussionIds: function () {
const ids = [];
discussion.createNote(noteObj); for (const discussionId in this.state) {
},
update: function (discussionId, noteId, resolved, resolved_by) {
const discussion = this.state[discussionId];
const note = discussion.getNote(noteId);
note.resolved = resolved;
note.resolved_by = resolved_by;
},
delete: function (discussionId, noteId) {
const discussion = this.state[discussionId]; const discussion = this.state[discussionId];
discussion.deleteNote(noteId);
if (discussion.notesCount() === 0) { if (!discussion.isResolved()) {
Vue.delete(this.state, discussionId); ids.push(discussion.id);
} }
},
unresolvedDiscussionIds: function () {
const ids = [];
for (const discussionId in this.state) {
const discussion = this.state[discussionId];
if (!discussion.isResolved()) {
ids.push(discussion.id);
}
}
return ids;
} }
};
})(window); return ids;
}
};
...@@ -5,129 +5,127 @@ require('~/diff_notes/models/discussion'); ...@@ -5,129 +5,127 @@ require('~/diff_notes/models/discussion');
require('~/diff_notes/models/note'); require('~/diff_notes/models/note');
require('~/diff_notes/stores/comments'); require('~/diff_notes/stores/comments');
(() => { function createDiscussion(noteId = 1, resolved = true) {
function createDiscussion(noteId = 1, resolved = true) { CommentsStore.create({
CommentsStore.create({ discussionId: 'a',
discussionId: 'a', noteId,
noteId, canResolve: true,
canResolve: true, resolved,
resolved, resolvedBy: 'test',
resolvedBy: 'test', authorName: 'test',
authorName: 'test', authorAvatar: 'test',
authorAvatar: 'test', noteTruncated: 'test...',
noteTruncated: 'test...',
});
}
beforeEach(() => {
CommentsStore.state = {};
}); });
}
describe('New discussion', () => { beforeEach(() => {
it('creates new discussion', () => { CommentsStore.state = {};
expect(Object.keys(CommentsStore.state).length).toBe(0); });
createDiscussion();
expect(Object.keys(CommentsStore.state).length).toBe(1);
});
it('creates new note in discussion', () => { describe('New discussion', () => {
createDiscussion(); it('creates new discussion', () => {
createDiscussion(2); expect(Object.keys(CommentsStore.state).length).toBe(0);
createDiscussion();
expect(Object.keys(CommentsStore.state).length).toBe(1);
});
const discussion = CommentsStore.state['a']; it('creates new note in discussion', () => {
expect(Object.keys(discussion.notes).length).toBe(2); createDiscussion();
}); createDiscussion(2);
const discussion = CommentsStore.state['a'];
expect(Object.keys(discussion.notes).length).toBe(2);
}); });
});
describe('Get note', () => { describe('Get note', () => {
beforeEach(() => { beforeEach(() => {
expect(Object.keys(CommentsStore.state).length).toBe(0); expect(Object.keys(CommentsStore.state).length).toBe(0);
createDiscussion(); createDiscussion();
}); });
it('gets note by ID', () => { it('gets note by ID', () => {
const note = CommentsStore.get('a', 1); const note = CommentsStore.get('a', 1);
expect(note).toBeDefined(); expect(note).toBeDefined();
expect(note.id).toBe(1); expect(note.id).toBe(1);
});
}); });
});
describe('Delete discussion', () => { describe('Delete discussion', () => {
beforeEach(() => { beforeEach(() => {
expect(Object.keys(CommentsStore.state).length).toBe(0); expect(Object.keys(CommentsStore.state).length).toBe(0);
createDiscussion(); createDiscussion();
}); });
it('deletes discussion by ID', () => { it('deletes discussion by ID', () => {
CommentsStore.delete('a', 1); CommentsStore.delete('a', 1);
expect(Object.keys(CommentsStore.state).length).toBe(0); expect(Object.keys(CommentsStore.state).length).toBe(0);
}); });
it('deletes discussion when no more notes', () => { it('deletes discussion when no more notes', () => {
createDiscussion(); createDiscussion();
createDiscussion(2); createDiscussion(2);
expect(Object.keys(CommentsStore.state).length).toBe(1); expect(Object.keys(CommentsStore.state).length).toBe(1);
expect(Object.keys(CommentsStore.state['a'].notes).length).toBe(2); expect(Object.keys(CommentsStore.state['a'].notes).length).toBe(2);
CommentsStore.delete('a', 1); CommentsStore.delete('a', 1);
CommentsStore.delete('a', 2); CommentsStore.delete('a', 2);
expect(Object.keys(CommentsStore.state).length).toBe(0); expect(Object.keys(CommentsStore.state).length).toBe(0);
});
}); });
});
describe('Update note', () => { describe('Update note', () => {
beforeEach(() => { beforeEach(() => {
expect(Object.keys(CommentsStore.state).length).toBe(0); expect(Object.keys(CommentsStore.state).length).toBe(0);
createDiscussion(); createDiscussion();
}); });
it('updates note to be unresolved', () => { it('updates note to be unresolved', () => {
CommentsStore.update('a', 1, false, 'test'); CommentsStore.update('a', 1, false, 'test');
const note = CommentsStore.get('a', 1); const note = CommentsStore.get('a', 1);
expect(note.resolved).toBe(false); expect(note.resolved).toBe(false);
});
}); });
});
describe('Discussion resolved', () => { describe('Discussion resolved', () => {
beforeEach(() => { beforeEach(() => {
expect(Object.keys(CommentsStore.state).length).toBe(0); expect(Object.keys(CommentsStore.state).length).toBe(0);
createDiscussion(); createDiscussion();
}); });
it('is resolved with single note', () => { it('is resolved with single note', () => {
const discussion = CommentsStore.state['a']; const discussion = CommentsStore.state['a'];
expect(discussion.isResolved()).toBe(true); expect(discussion.isResolved()).toBe(true);
}); });
it('is unresolved with 2 notes', () => { it('is unresolved with 2 notes', () => {
const discussion = CommentsStore.state['a']; const discussion = CommentsStore.state['a'];
createDiscussion(2, false); createDiscussion(2, false);
expect(discussion.isResolved()).toBe(false); expect(discussion.isResolved()).toBe(false);
}); });
it('is resolved with 2 notes', () => { it('is resolved with 2 notes', () => {
const discussion = CommentsStore.state['a']; const discussion = CommentsStore.state['a'];
createDiscussion(2); createDiscussion(2);
expect(discussion.isResolved()).toBe(true); expect(discussion.isResolved()).toBe(true);
}); });
it('resolve all notes', () => { it('resolve all notes', () => {
const discussion = CommentsStore.state['a']; const discussion = CommentsStore.state['a'];
createDiscussion(2, false); createDiscussion(2, false);
discussion.resolveAllNotes(); discussion.resolveAllNotes();
expect(discussion.isResolved()).toBe(true); expect(discussion.isResolved()).toBe(true);
}); });
it('unresolve all notes', () => { it('unresolve all notes', () => {
const discussion = CommentsStore.state['a']; const discussion = CommentsStore.state['a'];
createDiscussion(2); createDiscussion(2);
discussion.unResolveAllNotes(); discussion.unResolveAllNotes();
expect(discussion.isResolved()).toBe(false); expect(discussion.isResolved()).toBe(false);
});
}); });
})(); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment