
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
150 lines
4.2 KiB
JavaScript
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;
|
|
},
|
|
});
|
|
})();
|