Wyatt Allen f78a45c7eb Associate threads with in_reply_to in gr-diff-comment-thread-group
The UI does not allow creating multiple comments at the same diff
location, but instead encourages contributing to the comment threads
already at a location. The uniqueness of thread locations was leveraged
to simplify the collection of comments into threads by mapping their
locations to keys.

However, the REST API does permit multiple threads starting from the
same diff location, so this expectation in the key-based UI threading
logic can be wrong. In such scenarios, an unresolved thread may be
mistakenly inserted into a resolved thread, resulting in a comment that
is not resolvable through the UI.

With this change, the threading logic in gr-diff-comment-thread-group is
updated to associate comments into threads via the `in_reply_to` graph
rather than location map in the same way that the
`unresolved_comment_count` detail property is determined server-side.

Bug: Issue 6779
Change-Id: Ibb29d4ba5c5de9e92dfbfba8ac7ac8037c332028
2018-02-05 18:12:32 -08:00

150 lines
4.2 KiB
JavaScript

// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function() {
'use strict';
Polymer({
is: 'gr-diff-comment-thread-group',
properties: {
changeNum: String,
comments: {
type: Array,
value() { return []; },
},
projectName: String,
patchForNewThreads: String,
range: Object,
isOnParent: {
type: Boolean,
value: false,
},
parentIndex: {
type: Number,
value: null,
},
_threads: {
type: Array,
value() { return []; },
},
},
observers: [
'_commentsChanged(comments.*)',
],
addNewThread(locationRange) {
this.push('_threads', {
comments: [],
locationRange,
patchNum: this.patchForNewThreads,
});
},
removeThread(locationRange) {
for (let i = 0; i < this._threads.length; i++) {
if (this._threads[i].locationRange === locationRange) {
this.splice('_threads', i, 1);
return;
}
}
},
getThreadForRange(rangeToCheck) {
const threads = [].filter.call(
Polymer.dom(this.root).querySelectorAll('gr-diff-comment-thread'),
thread => {
return thread.locationRange === rangeToCheck;
});
if (threads.length === 1) {
return threads[0];
}
},
_commentsChanged() {
this._threads = this._getThreadGroups(this.comments);
},
_sortByDate(threadGroups) {
if (!threadGroups.length) { return; }
return threadGroups.sort((a, b) => {
// If a comment is a draft, it doesn't have a start_datetime yet.
// Assume it is newer than the comment it is being compared to.
if (!a.start_datetime) {
return 1;
}
if (!b.start_datetime) {
return -1;
}
return util.parseDate(a.start_datetime) -
util.parseDate(b.start_datetime);
});
},
_calculateLocationRange(range, comment) {
return 'range-' + range.start_line + '-' +
range.start_character + '-' +
range.end_line + '-' +
range.end_character + '-' +
comment.__commentSide;
},
/**
* Determines what the patchNum of a thread should be. Use patchNum from
* comment if it exists, otherwise the property of the thread group.
* This is needed for switching between side-by-side and unified views when
* there are unsaved drafts.
*/
_getPatchNum(comment) {
return comment.patchNum || this.patchForNewThreads;
},
_getThreadGroups(comments) {
const sortedComments = comments.slice(0).sort((a, b) =>
util.parseDate(a.updated) - util.parseDate(b.updated));
const threads = [];
for (const comment of sortedComments) {
let locationRange;
if (!comment.range) {
locationRange = 'line-' + comment.__commentSide;
} else {
locationRange = this._calculateLocationRange(comment.range, comment);
}
// If the comment is in reply to another comment, find that comment's
// thread and append to it.
if (comment.in_reply_to) {
const thread = threads.find(thread =>
thread.comments.some(c => c.id === comment.in_reply_to));
if (thread) {
thread.comments.push(comment);
continue;
}
}
// Otherwise, this comment starts its own thread.
threads.push({
start_datetime: comment.updated,
comments: [comment],
locationRange,
commentSide: comment.__commentSide,
patchNum: this._getPatchNum(comment),
});
}
return threads;
},
});
})();