From 36589888936776579514fe7d270ea9edae83e34a Mon Sep 17 00:00:00 2001 From: Ben Rohlfs Date: Sat, 26 Sep 2020 11:26:02 +0200 Subject: [PATCH 1/2] Rename files to preserve history Test\Eslint fail - this is expected. Change-Id: Ic06c02e6432b6a07c89167e2532944eef4136364 --- .../diff/gr-diff-host/{gr-diff-host.js => gr-diff-host.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename polygerrit-ui/app/elements/diff/gr-diff-host/{gr-diff-host.js => gr-diff-host.ts} (100%) diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts similarity index 100% rename from polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js rename to polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts From 1d487069faa1a5101bbf89aa88fcb2cc66309722 Mon Sep 17 00:00:00 2001 From: Ben Rohlfs Date: Sat, 26 Sep 2020 11:26:03 +0200 Subject: [PATCH 2/2] Convert gr-diff-host to typescript The change converts the following files to typescript: * elements/diff/gr-diff-host/gr-diff-host.ts Change-Id: Ie565ab753fea6909790729b8e42410d39c437612 --- .../change/gr-file-list/gr-file-list_test.js | 6 +- .../change/gr-reply-dialog/gr-reply-dialog.ts | 12 +- .../change/gr-thread-list/gr-thread-list.ts | 30 +- .../gr-thread-list/gr-thread-list_test.js | 2 +- .../gr-apply-fix-dialog.ts | 8 +- .../diff/gr-comment-api/gr-comment-api.ts | 180 +-- .../gr-comment-api/gr-comment-api_test.js | 31 +- .../gr-coverage-layer/gr-coverage-layer.ts | 6 +- .../gr-diff-builder-element.ts | 4 +- .../diff/gr-diff-builder/gr-diff-builder.ts | 20 +- .../diff/gr-diff-cursor/gr-diff-cursor.ts | 35 +- .../diff/gr-diff-host/gr-diff-host.ts | 1216 +++++++++-------- .../diff/gr-diff-host/gr-diff-host_test.js | 78 +- .../gr-diff-processor/gr-diff-processor.ts | 6 +- .../elements/diff/gr-diff/gr-diff-utils.ts | 7 +- .../app/elements/diff/gr-diff/gr-diff.ts | 14 +- .../gr-patch-range-select.ts | 8 +- .../gr-patch-range-select_test.js | 4 +- .../gr-comment-thread/gr-comment-thread.ts | 76 +- .../elements/shared/gr-comment/gr-comment.ts | 69 +- .../gr-annotation-actions-js-api.ts | 35 +- .../gr-js-api-interface-element.ts | 15 +- .../gr-js-api-interface/gr-js-api-types.ts | 5 + .../gr-rest-api-interface.ts | 13 +- .../services/gr-rest-api/gr-rest-api.ts | 28 +- polygerrit-ui/app/types/common.ts | 16 +- polygerrit-ui/app/types/types.ts | 6 + polygerrit-ui/app/utils/patch-set-util.ts | 7 + 28 files changed, 1040 insertions(+), 897 deletions(-) diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js index 126c33047c..8aebf9b3a3 100644 --- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js +++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js @@ -27,6 +27,7 @@ import {runA11yAudit} from '../../../test/a11y-test-utils.js'; import {html} from '@polymer/polymer/lib/utils/html-tag.js'; import {TestKeyboardShortcutBinder} from '../../../test/test-utils.js'; import {Shortcut} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.js'; +import {ChangeComments} from '../../diff/gr-comment-api/gr-comment-api.js'; const commentApiMock = createCommentApiMockWithTemplateElement( 'gr-file-list-comment-api-mock', html` @@ -351,7 +352,7 @@ suite('gr-file-list tests', () => { }); test('comment filtering', () => { - element.changeComments._comments = { + const comments = { '/COMMIT_MSG': [ {patch_set: 1, message: 'Done', updated: '2017-02-08 16:40:49'}, {patch_set: 1, message: 'oh hay', updated: '2017-02-09 16:40:49'}, @@ -387,7 +388,7 @@ suite('gr-file-list tests', () => { }, ], }; - element.changeComments._drafts = { + const drafts = { '/COMMIT_MSG': [ { patch_set: 1, @@ -414,6 +415,7 @@ suite('gr-file-list tests', () => { }, ], }; + element.changeComments = new ChangeComments(comments, {}, drafts, 123); const parentTo1 = { basePatchNum: 'PARENT', diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts index 208e766327..de56e9db79 100644 --- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts +++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts @@ -97,11 +97,8 @@ import { PolymerSplice, PolymerSpliceChange, } from '@polymer/polymer/interfaces'; -import {assertNever, hasOwnProperty} from '../../../utils/common-util'; -import { - CommentThread, - HumanCommentInfoWithPath, -} from '../../diff/gr-comment-api/gr-comment-api'; +import {assertNever} from '../../../utils/common-util'; +import {CommentThread, isDraft} from '../../diff/gr-comment-api/gr-comment-api'; import {GrTextarea} from '../../shared/gr-textarea/gr-textarea'; import {GrAccountChip} from '../../shared/gr-account-chip/gr-account-chip'; import {GrOverlay} from '../../shared/gr-overlay/gr-overlay'; @@ -1029,10 +1026,7 @@ export class GrReplyDialog extends KeyboardShortcutMixin( _containsNewCommentThread(threads: CommentThread[]) { return threads.some( thread => - !!thread.comments && - !!thread.comments[0] && - hasOwnProperty(thread.comments[0], '__draft') && - !!(thread.comments[0] as HumanCommentInfoWithPath).__draft + !!thread.comments && !!thread.comments[0] && isDraft(thread.comments[0]) ); } diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts index 52ee722571..4b020756c0 100644 --- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts +++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts @@ -26,9 +26,12 @@ import {parseDate} from '../../../utils/date-util'; import {NO_THREADS_MSG} from '../../../constants/messages'; import {CommentSide, SpecialFilePath} from '../../../constants/constants'; -import {customElement, property, observe} from '@polymer/decorators'; -import {CommentThread} from '../../diff/gr-comment-api/gr-comment-api'; -import {Comment, RobotComment} from '../../shared/gr-comment/gr-comment'; +import {customElement, observe, property} from '@polymer/decorators'; +import { + CommentThread, + isDraft, + UIRobot, +} from '../../diff/gr-comment-api/gr-comment-api'; import {PolymerSpliceChange} from '@polymer/polymer/interfaces'; import {ChangeInfo} from '../../../types/common'; @@ -303,27 +306,32 @@ export class GrThreadList extends GestureEventListeners( _getThreadWithStatusInfo(thread: CommentThread): CommentThreadWithInfo { const comments = thread.comments; - const lastComment = (comments[comments.length - 1] || {}) as Comment; + const lastComment = comments.length + ? comments[comments.length - 1] + : undefined; let hasRobotComment = false; let hasHumanReplyToRobotComment = false; comments.forEach(comment => { - if ((comment as RobotComment).robot_id) { + if ((comment as UIRobot).robot_id) { hasRobotComment = true; } else if (hasRobotComment) { hasHumanReplyToRobotComment = true; } }); + let updated = undefined; + if (lastComment) { + if (isDraft(lastComment)) updated = lastComment.__date; + if (lastComment.updated) updated = parseDate(lastComment.updated); + } return { thread, hasRobotComment, hasHumanReplyToRobotComment, - unresolved: !!lastComment.unresolved, - isEditing: !!lastComment.__editing, - hasDraft: !!lastComment.__draft, - updated: lastComment.updated - ? parseDate(lastComment.updated) - : lastComment.__date, + unresolved: !!lastComment && !!lastComment.unresolved, + isEditing: !!lastComment && !!lastComment.__editing, + hasDraft: !!lastComment && isDraft(lastComment), + updated, }; } diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.js b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.js index 92010d2e8b..bad3a99f8d 100644 --- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.js +++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.js @@ -154,7 +154,7 @@ suite('gr-thread-list tests', () => { message: 'resolved draft', unresolved: false, __draft: true, - __draftID: '0.m683trwff68', + __draftID: '0.m683trwff69', __editing: false, patch_set: '2', }, diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts index d27590a7af..675a048bf0 100644 --- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts +++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts @@ -38,11 +38,9 @@ import { RobotId, } from '../../../types/common'; import {GrOverlay} from '../../shared/gr-overlay/gr-overlay'; -import { - CommentEventDetail, - isRobotComment, -} from '../../shared/gr-comment/gr-comment'; +import {CommentEventDetail} from '../../shared/gr-comment/gr-comment'; import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api'; +import {isRobot} from '../gr-comment-api/gr-comment-api'; export interface GrApplyFixDialog { $: { @@ -115,7 +113,7 @@ export class GrApplyFixDialog extends GestureEventListeners( open(e: CustomEvent) { const detail = e.detail; const comment = detail.comment; - if (!detail.patchNum || !comment || !isRobotComment(comment)) { + if (!detail.patchNum || !comment || !isRobot(comment)) { return Promise.resolve(); } this._patchNum = detail.patchNum; diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.ts b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.ts index 0830b83220..deecdbf77e 100644 --- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.ts +++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.ts @@ -27,12 +27,12 @@ import { } from '../../../utils/patch-set-util'; import {customElement, property} from '@polymer/decorators'; import { + CommentBasics, CommentInfo, ConfigInfo, ParentPatchSetNum, PatchRange, PatchSetNum, - PathToCommentsInfoMap, PathToRobotCommentsInfoMap, RobotCommentInfo, Timestamp, @@ -40,36 +40,61 @@ import { NumericChangeId, } from '../../../types/common'; import {hasOwnProperty} from '../../../utils/common-util'; -import {CommentSide} from '../../../constants/constants'; +import {CommentSide, Side} from '../../../constants/constants'; import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api'; -export interface HumanCommentInfoWithPath extends CommentInfo { - path: string; +export interface DraftCommentProps { __draft?: boolean; + __draftID?: string; __date?: Date; } -export interface RobotCommentInfoWithPath extends RobotCommentInfo { - path: string; +export type DraftInfo = CommentBasics & DraftCommentProps; + +/** + * Each of the type implements or extends CommentBasics. + */ +export type Comment = DraftInfo | CommentInfo | RobotCommentInfo; + +export interface UIStateCommentProps { + // The `side` of the comment is PARENT or REVISION, but this is LEFT or RIGHT. + // TODO(TS): Remove the naming confusion of commentSide being of type of Side, + // but side being of type CommentSide. :-) + __commentSide?: Side; + // TODO(TS): Remove this. Seems to be exactly the same as `path`?? + __path?: string; + collapsed?: boolean; + // TODO(TS): Consider allowing this only for drafts. + __editing?: boolean; + __otherEditing?: boolean; } -export type CommentInfoWithPath = - | HumanCommentInfoWithPath - | RobotCommentInfoWithPath; +export type UIDraft = DraftInfo & UIStateCommentProps; -// TODO(TS): Can be removed, CommentInfoWithTwoPaths already has a path -export type CommentInfoWithTwoPaths = CommentInfoWithPath & {__path: string}; +export type UIHuman = CommentInfo & UIStateCommentProps; -export type PathToCommentsInfoWithPathMap = { - [path: string]: CommentInfoWithPath[]; -}; +export type UIRobot = RobotCommentInfo & UIStateCommentProps; + +export type UIComment = UIHuman | UIRobot | UIDraft; export type CommentMap = {[path: string]: boolean}; +export function isRobot( + x: T | DraftInfo | RobotCommentInfo | undefined +): x is RobotCommentInfo { + return !!x && !!(x as RobotCommentInfo).robot_id; +} + +export function isDraft( + x: T | UIDraft | undefined +): x is UIDraft { + return !!x && !!(x as UIDraft).__draft; +} + export interface PatchSetFile { path: string; basePath?: string; - patchNum: PatchSetNum; + patchNum?: PatchSetNum; } export interface PatchNumOnly { @@ -107,9 +132,13 @@ export function sortComments(comments: T[]): T[] { } export interface CommentThread { - comments: CommentInfoWithTwoPaths[]; + comments: UIComment[]; patchNum?: PatchSetNum; path: string; + // TODO(TS): It would be nice to use LineNumber here, but the comment thread + // element actually relies on line to be undefined for file comments. Be + // aware of element attribute getters and setters, if you try to refactor + // this. :-) Still worthwhile to do ... line?: number; rootId: UrlEncodedCommentId; commentSide?: CommentSide; @@ -119,7 +148,7 @@ export type CommentIdToCommentThreadMap = { [urlEncodedCommentId: string]: CommentThread; }; -interface TwoSidesComments { +export interface TwoSidesComments { // TODO(TS): remove meta - it is not used anywhere meta: { changeNum: NumericChangeId; @@ -127,16 +156,16 @@ interface TwoSidesComments { patchRange: PatchRange; projectConfig?: ConfigInfo; }; - left: CommentInfoWithPath[]; - right: CommentInfoWithPath[]; + left: UIComment[]; + right: UIComment[]; } export class ChangeComments { - private readonly _comments: PathToCommentsInfoWithPathMap; + private readonly _comments: {[path: string]: UIHuman[]}; - private readonly _robotComments: PathToCommentsInfoWithPathMap; + private readonly _robotComments: {[path: string]: UIRobot[]}; - private readonly _drafts: PathToCommentsInfoWithPathMap; + private readonly _drafts: {[path: string]: UIDraft[]}; private readonly _changeNum: NumericChangeId; @@ -145,9 +174,9 @@ export class ChangeComments { * elements of that which uses the gr-comment-api. */ constructor( - comments: PathToCommentsInfoMap | undefined, - robotComments: PathToRobotCommentsInfoMap | undefined, - drafts: PathToCommentsInfoMap | undefined, + comments: {[path: string]: UIHuman[]} | undefined, + robotComments: {[path: string]: UIRobot[]} | undefined, + drafts: {[path: string]: UIDraft[]} | undefined, changeNum: NumericChangeId ) { this._comments = this._addPath(comments); @@ -164,10 +193,10 @@ export class ChangeComments { * TODO(taoalpha): should consider changing BE to send path * back within CommentInfo */ - _addPath( - comments: PathToCommentsInfoMap = {} - ): PathToCommentsInfoWithPathMap { - const updatedComments: PathToCommentsInfoWithPathMap = {}; + _addPath( + comments: {[path: string]: T[]} = {} + ): {[path: string]: Array} { + const updatedComments: {[path: string]: Array} = {}; for (const filePath of Object.keys(comments)) { const allCommentsForPath = comments[filePath] || []; if (allCommentsForPath.length) { @@ -191,10 +220,8 @@ export class ChangeComments { return this._robotComments; } - findCommentById( - commentId: UrlEncodedCommentId - ): HumanCommentInfoWithPath | RobotCommentInfoWithPath | undefined { - const findComment = (comments: PathToCommentsInfoWithPathMap) => { + findCommentById(commentId: UrlEncodedCommentId): Comment | undefined { + const findComment = (comments: {[path: string]: CommentBasics[]}) => { let comment; for (const path of Object.keys(comments)) { comment = comment || comments[path].find(c => c.id === commentId); @@ -216,7 +243,11 @@ export class ChangeComments { * patchNum and basePatchNum properties to represent the range. */ getPaths(patchRange?: PatchRange): CommentMap { - const responses = [this.comments, this.drafts, this.robotComments]; + const responses: {[path: string]: UIComment[]}[] = [ + this.comments, + this.drafts, + this.robotComments, + ]; const commentMap: CommentMap = {}; for (const response of responses) { for (const path in response) { @@ -240,9 +271,7 @@ export class ChangeComments { /** * Gets all the comments and robot comments for the given change. */ - getAllPublishedComments( - patchNum?: PatchSetNum - ): PathToCommentsInfoWithPathMap { + getAllPublishedComments(patchNum?: PatchSetNum) { return this.getAllComments(false, patchNum); } @@ -263,12 +292,9 @@ export class ChangeComments { /** * Gets all the comments and robot comments for the given change. */ - getAllComments( - includeDrafts?: boolean, - patchNum?: PatchSetNum - ): PathToCommentsInfoWithPathMap { + getAllComments(includeDrafts?: boolean, patchNum?: PatchSetNum) { const paths = this.getPaths(); - const publishedComments: PathToCommentsInfoWithPathMap = {}; + const publishedComments: {[path: string]: CommentBasics[]} = {}; for (const path of Object.keys(paths)) { publishedComments[path] = this.getAllCommentsForPath( path, @@ -282,9 +308,9 @@ export class ChangeComments { /** * Gets all the drafts for the given change. */ - getAllDrafts(patchNum?: PatchSetNum): PathToCommentsInfoWithPathMap { + getAllDrafts(patchNum?: PatchSetNum) { const paths = this.getPaths(); - const drafts: PathToCommentsInfoWithPathMap = {}; + const drafts: {[path: string]: UIDraft[]} = {}; for (const path of Object.keys(paths)) { drafts[path] = this.getAllDraftsForPath(path, patchNum); } @@ -302,8 +328,8 @@ export class ChangeComments { path: string, patchNum?: PatchSetNum, includeDrafts?: boolean - ): CommentInfoWithPath[] { - const comments = this._comments[path] || []; + ): Comment[] { + const comments: Comment[] = this._comments[path] || []; const robotComments = this._robotComments[path] || []; let allComments = comments.concat(robotComments); if (includeDrafts) { @@ -347,10 +373,7 @@ export class ChangeComments { * This will return a shallow copy of all drafts every time, * so changes on any copy will not affect other copies. */ - getAllDraftsForPath( - path: string, - patchNum?: PatchSetNum - ): CommentInfoWithPath[] { + getAllDraftsForPath(path: string, patchNum?: PatchSetNum): Comment[] { let comments = this._drafts[path] || []; if (patchNum) { comments = comments.filter(c => patchNumEquals(c.patch_set, patchNum)); @@ -365,7 +388,7 @@ export class ChangeComments { * * // TODO(taoalpha): maybe merge in *ForPath */ - getAllDraftsForFile(file: PatchSetFile): CommentInfoWithPath[] { + getAllDraftsForFile(file: PatchSetFile): Comment[] { let allDrafts = this.getAllDraftsForPath(file.path, file.patchNum); if (file.basePath) { allDrafts = allDrafts.concat( @@ -390,9 +413,9 @@ export class ChangeComments { patchRange: PatchRange, projectConfig?: ConfigInfo ): TwoSidesComments { - let comments: CommentInfoWithPath[] = []; - let drafts: CommentInfoWithPath[] = []; - let robotComments: CommentInfoWithPath[] = []; + let comments: Comment[] = []; + let drafts: DraftInfo[] = []; + let robotComments: RobotCommentInfo[] = []; if (this.comments && this.comments[path]) { comments = this.comments[path]; } @@ -404,11 +427,10 @@ export class ChangeComments { } drafts.forEach(d => { - // drafts don't include robot comments - (d as HumanCommentInfoWithPath).__draft = true; + d.__draft = true; }); - const all = comments + const all: Comment[] = comments .concat(drafts) .concat(robotComments) .map(c => { @@ -450,7 +472,7 @@ export class ChangeComments { file: PatchSetFile, patchRange: PatchRange, projectConfig?: ConfigInfo - ) { + ): TwoSidesComments { const comments = this.getCommentsBySideForPath( file.path, patchRange, @@ -476,24 +498,22 @@ export class ChangeComments { * also includes the file that it was left on, which was the key of the * originall object. */ - _commentObjToArrayWithFile( - comments: PathToCommentsInfoWithPathMap - ): CommentInfoWithTwoPaths[] { - let commentArr: CommentInfoWithTwoPaths[] = []; + _commentObjToArrayWithFile(comments: { + [path: string]: T[]; + }): Array { + let commentArr: Array = []; for (const file of Object.keys(comments)) { - const commentsForFile = []; + const commentsForFile: Array = []; for (const comment of comments[file]) { - commentsForFile.push({__path: file, ...comment}); + commentsForFile.push({...comment, __path: file}); } commentArr = commentArr.concat(commentsForFile); } return commentArr; } - _commentObjToArray( - comments: PathToCommentsInfoWithPathMap - ): CommentInfoWithPath[] { - let commentArr: CommentInfoWithPath[] = []; + _commentObjToArray(comments: {[path: string]: T[]}): T[] { + let commentArr: T[] = []; for (const file of Object.keys(comments)) { commentArr = commentArr.concat(comments[file]); } @@ -527,8 +547,8 @@ export class ChangeComments { * Computes a number of unresolved comment threads in a given file and path. */ computeUnresolvedNum(file: PatchSetFile | PatchNumOnly) { - let comments: CommentInfoWithPath[] = []; - let drafts: CommentInfoWithPath[] = []; + let comments: Comment[] = []; + let drafts: Comment[] = []; if (isPatchSetFile(file)) { comments = this.getAllCommentsForFile(file); @@ -541,12 +561,7 @@ export class ChangeComments { comments = comments.concat(drafts); - // TODO(TS): the 'as CommentInfoWithTwoPaths[]' is completely wrong below - // However, this doesn't affect the final result of computeUnresolvedNum - // This should be fixed by removing CommentInfoWithTwoPaths later - const threads = this.getCommentThreads( - sortComments(comments) as CommentInfoWithTwoPaths[] - ); + const threads = this.getCommentThreads(sortComments(comments)); const unresolvedThreads = threads.filter( thread => @@ -568,7 +583,7 @@ export class ChangeComments { * * @param comments sorted by updated timestamp. */ - getCommentThreads(comments: CommentInfoWithTwoPaths[]) { + getCommentThreads(comments: UIComment[]) { const threads: CommentThread[] = []; const idThreadMap: CommentIdToCommentThreadMap = {}; for (const comment of comments) { @@ -585,10 +600,13 @@ export class ChangeComments { } // Otherwise, this comment starts its own thread. + if (!comment.__path && !comment.path) { + throw new Error('Comment missing required "path".'); + } const newThread: CommentThread = { comments: [comment], patchNum: comment.patch_set, - path: comment.__path, + path: comment.__path || comment.path!, line: comment.line, rootId: comment.id, }; @@ -605,7 +623,7 @@ export class ChangeComments { * Whether the given comment should be included in the base side of the * given patch range. */ - _isInBaseOfPatchRange(comment: CommentInfo, range: PatchRange) { + _isInBaseOfPatchRange(comment: CommentBasics, range: PatchRange) { // If the base of the patch range is a parent of a merge, and the comment // appears on a specific parent then only show the comment if the parent // index of the comment matches that of the range. @@ -636,7 +654,7 @@ export class ChangeComments { * Whether the given comment should be included in the revision side of the * given patch range. */ - _isInRevisionOfPatchRange(comment: CommentInfo, range: PatchRange) { + _isInRevisionOfPatchRange(comment: CommentBasics, range: PatchRange) { return ( comment.side !== CommentSide.PARENT && patchNumEquals(comment.patch_set, range.patchNum) @@ -646,7 +664,7 @@ export class ChangeComments { /** * Whether the given comment should be included in the given patch range. */ - _isInPatchRange(comment: CommentInfo, range: PatchRange): boolean { + _isInPatchRange(comment: CommentBasics, range: PatchRange): boolean { return ( this._isInBaseOfPatchRange(comment, range) || this._isInRevisionOfPatchRange(comment, range) diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.js b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.js index bfe74c23d7..2cf9bc1c3a 100644 --- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.js +++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.js @@ -17,6 +17,7 @@ import '../../../test/common-test-setup-karma.js'; import './gr-comment-api.js'; +import {ChangeComments} from './gr-comment-api.js'; const basicFixture = fixtureFromElement('gr-comment-api'); @@ -216,7 +217,7 @@ suite('gr-comment-api tests', () => { } setup(() => { - element._changeComments._drafts = { + const drafts = { 'file/one': [ { id: '11', @@ -244,7 +245,7 @@ suite('gr-comment-api tests', () => { }, ], }; - element._changeComments._robotComments = { + const robotComments = { 'file/one': [ { id: '01', @@ -268,7 +269,7 @@ suite('gr-comment-api tests', () => { }, ], }; - element._changeComments._comments = { + const comments = { 'file/one': [ { id: '03', @@ -305,6 +306,8 @@ suite('gr-comment-api tests', () => { {id: '10', patch_set: 5, line: 1, updated: makeTime(1)}, ], }; + element._changeComments = + new ChangeComments(comments, robotComments, drafts, 1234); }); test('getPaths', () => { @@ -405,9 +408,7 @@ suite('gr-comment-api tests', () => { }); test('computeUnresolvedNum w/ non-linear thread', () => { - element._changeComments._drafts = {}; - element._changeComments._robotComments = {}; - element._changeComments._comments = { + const comments = { path: [{ id: '9c6ba3c6_28b7d467', patch_set: 1, @@ -433,6 +434,7 @@ suite('gr-comment-api tests', () => { unresolved: false, }], }; + element._changeComments = new ChangeComments(comments, {}, {}, 1234); assert.equal( element._changeComments.computeUnresolvedNum(1, 'path'), 0); }); @@ -523,6 +525,7 @@ suite('gr-comment-api tests', () => { end_line: 2, end_character: 2, }, + path: 'file/one', __path: 'file/one', }, ], @@ -538,6 +541,7 @@ suite('gr-comment-api tests', () => { patch_set: 2, side: 'PARENT', line: 2, + path: 'file/one', __path: 'file/one', updated: '2013-02-26 15:01:43.986000000', }, @@ -553,6 +557,7 @@ suite('gr-comment-api tests', () => { id: '04', patch_set: 2, line: 1, + path: 'file/one', __path: 'file/one', updated: '2013-02-26 15:01:43.986000000', }, @@ -562,6 +567,7 @@ suite('gr-comment-api tests', () => { patch_set: 2, unresolved: true, line: 1, + path: 'file/one', __path: 'file/one', updated: '2013-02-26 15:03:43.986000000', }, @@ -570,6 +576,7 @@ suite('gr-comment-api tests', () => { in_reply_to: '04', patch_set: 2, line: 1, + path: 'file/one', __path: 'file/one', __draft: true, updated: '2013-02-26 15:02:43.986000000', @@ -585,6 +592,7 @@ suite('gr-comment-api tests', () => { id: '05', patch_set: 2, line: 2, + path: 'file/two', __path: 'file/two', updated: '2013-02-26 15:01:43.986000000', }, @@ -599,6 +607,7 @@ suite('gr-comment-api tests', () => { id: '06', patch_set: 3, line: 2, + path: 'file/two', __path: 'file/two', updated: '2013-02-26 15:01:43.986000000', }, @@ -615,6 +624,7 @@ suite('gr-comment-api tests', () => { side: 'PARENT', unresolved: true, line: 1, + path: 'file/three', __path: 'file/three', updated: '2013-02-26 15:01:43.986000000', }, @@ -630,6 +640,7 @@ suite('gr-comment-api tests', () => { id: '08', patch_set: 3, line: 1, + path: 'file/three', __path: 'file/three', updated: '2013-02-26 15:01:43.986000000', }, @@ -645,6 +656,7 @@ suite('gr-comment-api tests', () => { patch_set: 5, side: 'PARENT', line: 1, + path: 'file/four', __path: 'file/four', updated: '2013-02-26 15:01:43.986000000', }, @@ -660,6 +672,7 @@ suite('gr-comment-api tests', () => { id: '10', patch_set: 5, line: 1, + path: 'file/four', __path: 'file/four', updated: '2013-02-26 15:01:43.986000000', }, @@ -674,6 +687,7 @@ suite('gr-comment-api tests', () => { id: '05', patch_set: 3, line: 1, + path: 'file/two', __path: 'file/two', __draft: true, updated: '2013-02-26 15:03:43.986000000', @@ -690,6 +704,7 @@ suite('gr-comment-api tests', () => { patch_set: 2, side: 'PARENT', line: 1, + path: 'file/one', __path: 'file/one', __draft: true, updated: '2013-02-26 15:03:43.986000000', @@ -710,6 +725,7 @@ suite('gr-comment-api tests', () => { let expectedComments = [ { __path: 'file/one', + path: 'file/one', id: '04', patch_set: 2, line: 1, @@ -717,6 +733,7 @@ suite('gr-comment-api tests', () => { }, { __path: 'file/one', + path: 'file/one', id: '02', in_reply_to: '04', patch_set: 2, @@ -726,6 +743,7 @@ suite('gr-comment-api tests', () => { }, { __path: 'file/one', + path: 'file/one', __draft: true, id: '12', in_reply_to: '04', @@ -742,6 +760,7 @@ suite('gr-comment-api tests', () => { patch_set: 2, side: 'PARENT', line: 1, + path: 'file/one', __path: 'file/one', __draft: true, updated: '2013-02-26 15:03:43.986000000', diff --git a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.ts b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.ts index 70629f4f01..2167641d06 100644 --- a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.ts +++ b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.ts @@ -18,7 +18,7 @@ import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-l import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin'; import {PolymerElement} from '@polymer/polymer/polymer-element'; import {htmlTemplate} from './gr-coverage-layer_html'; -import {CoverageType, DiffLayer} from '../../../types/types'; +import {CoverageRange, CoverageType, DiffLayer} from '../../../types/types'; import {customElement, property} from '@polymer/decorators'; declare global { @@ -45,11 +45,9 @@ export class GrCoverageLayer /** * Must be sorted by code_range.start_line. * Must only contain ranges that match the side. - * */ - // TODO(TS): convert into AnnotationLayer once gr-diff-is converted @property({type: Array}) - coverageRanges: any[] = []; + coverageRanges: CoverageRange[] = []; @property({type: String}) side?: string; diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts index aee6ab9154..52565649b0 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts @@ -317,9 +317,7 @@ export class GrDiffBuilderElement extends GestureEventListeners( } getSideByLineEl(lineEl: Element) { - return lineEl.classList.contains(GrDiffBuilder.Side.RIGHT) - ? Side.RIGHT - : Side.LEFT; + return lineEl.classList.contains(Side.RIGHT) ? Side.RIGHT : Side.LEFT; } emitGroup(group: GrDiffGroup, sectionEl: HTMLElement) { diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts index 5c9b998cf3..d1f52ae0fc 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts @@ -24,6 +24,7 @@ import { } from '../gr-diff/gr-diff-group'; import {BlameInfo, DiffInfo, DiffPreferencesInfo} from '../../../types/common'; import {Side} from '../../../constants/constants'; +import {DiffLayer} from '../../../types/types'; /** * In JS, unicode code points above 0xFFFF occupy two elements of a string. @@ -86,8 +87,7 @@ export abstract class GrDiffBuilder { diff: DiffInfo, prefs: DiffPreferencesInfo, outputEl: HTMLElement, - // TODO(TS): Replace any by a layer interface. - readonly layers: any[] = [] + readonly layers: DiffLayer[] = [] ) { this._diff = diff; this._numLinesLeft = this._diff.content @@ -142,12 +142,6 @@ export abstract class GrDiffBuilder { REMOVED: 'edit_a', }; - // TODO(TS): Convert to enum. - static readonly Side = { - LEFT: 'left', - RIGHT: 'right', - }; - // TODO(TS): Replace usages with ContextButtonType enum. static readonly ContextButtonType = { ABOVE: 'above', @@ -480,10 +474,14 @@ export abstract class GrDiffBuilder { contentText.setAttribute('data-side', side); } - for (const layer of this.layers) { - if (typeof layer.annotate === 'function') { - layer.annotate(contentText, lineNumberEl, line); + if (lineNumberEl) { + for (const layer of this.layers) { + if (typeof layer.annotate === 'function') { + layer.annotate(contentText, lineNumberEl, line); + } } + } else { + console.error('The lineNumberEl is null, skipping layer annotations.'); } td.appendChild(contentText); diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.ts b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.ts index 9937ebbfd9..1896effd94 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.ts @@ -26,9 +26,8 @@ import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-l import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin'; import {PolymerElement} from '@polymer/polymer/polymer-element'; import {htmlTemplate} from './gr-diff-cursor_html'; -import {ScrollMode} from '../../../constants/constants'; +import {ScrollMode, Side} from '../../../constants/constants'; import {customElement, property, observe} from '@polymer/decorators'; -import {DiffSide} from '../gr-diff/gr-diff-utils'; import {GrDiffLineType} from '../gr-diff/gr-diff-line'; import {PolymerSpliceChange} from '@polymer/polymer/interfaces'; import {PolymerDomWrapper} from '../../../types/types'; @@ -83,7 +82,7 @@ export class GrDiffCursor extends GestureEventListeners( private _lastDisplayedNavigateToNextFileToast: number | null = null; @property({type: String}) - side = DiffSide.RIGHT; + side = Side.RIGHT; @property({type: Object, notify: true, observer: '_rowChanged'}) diffRow?: HTMLElement; @@ -162,14 +161,14 @@ export class GrDiffCursor extends GestureEventListeners( } moveLeft() { - this.side = DiffSide.LEFT; + this.side = Side.LEFT; if (this._isTargetBlank()) { this.moveUp(); } } moveRight() { - this.side = DiffSide.RIGHT; + this.side = Side.RIGHT; if (this._isTargetBlank()) { this.moveUp(); } @@ -272,7 +271,7 @@ export class GrDiffCursor extends GestureEventListeners( this._fixSide(); } - moveToLineNumber(number: number, side: DiffSide, path?: string) { + moveToLineNumber(number: number, side: Side, path?: string) { const row = this._findRowByNumberAndFile(number, side, path); if (row) { this.side = side; @@ -291,7 +290,7 @@ export class GrDiffCursor extends GestureEventListeners( } if (this._getViewMode() === DiffViewMode.SIDE_BY_SIDE) { - lineElSelector += this.side === DiffSide.LEFT ? '.left' : '.right'; + lineElSelector += this.side === Side.LEFT ? '.left' : '.right'; } return this.diffRow.querySelector(lineElSelector); @@ -452,7 +451,7 @@ export class GrDiffCursor extends GestureEventListeners( _rowHasSide(row: Element) { const selector = - (this.side === DiffSide.LEFT ? '.left' : '.right') + ' + .content'; + (this.side === Side.LEFT ? '.left' : '.right') + ' + .content'; return !!row.querySelector(selector); } @@ -478,7 +477,7 @@ export class GrDiffCursor extends GestureEventListeners( this._getViewMode() === DiffViewMode.SIDE_BY_SIDE && this._isTargetBlank() ) { - this.side = this.side === DiffSide.LEFT ? DiffSide.RIGHT : DiffSide.LEFT; + this.side = this.side === Side.LEFT ? Side.RIGHT : Side.LEFT; } } @@ -489,8 +488,8 @@ export class GrDiffCursor extends GestureEventListeners( const actions = this._getActionsForRow(); return ( - (this.side === DiffSide.LEFT && !actions.left) || - (this.side === DiffSide.RIGHT && !actions.right) + (this.side === Side.LEFT && !actions.left) || + (this.side === Side.RIGHT && !actions.right) ); } @@ -506,16 +505,8 @@ export class GrDiffCursor extends GestureEventListeners( if (!this.diffRow) { return; } - this.toggleClass( - LEFT_SIDE_CLASS, - this.side === DiffSide.LEFT, - this.diffRow - ); - this.toggleClass( - RIGHT_SIDE_CLASS, - this.side === DiffSide.RIGHT, - this.diffRow - ); + this.toggleClass(LEFT_SIDE_CLASS, this.side === Side.LEFT, this.diffRow); + this.toggleClass(RIGHT_SIDE_CLASS, this.side === Side.RIGHT, this.diffRow); } _isActionType(type: GrDiffRowType) { @@ -601,7 +592,7 @@ export class GrDiffCursor extends GestureEventListeners( _findRowByNumberAndFile( targetNumber: number, - side: DiffSide, + side: Side, path?: string ): HTMLElement | undefined { let stops; diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts index 02d8cfae00..49826bb98f 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts @@ -14,21 +14,62 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js'; -import '../../shared/gr-comment-thread/gr-comment-thread.js'; -import '../../shared/gr-js-api-interface/gr-js-api-interface.js'; -import '../gr-diff/gr-diff.js'; -import '../gr-syntax-layer/gr-syntax-layer.js'; -import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js'; -import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js'; -import {PolymerElement} from '@polymer/polymer/polymer-element.js'; -import {htmlTemplate} from './gr-diff-host_html.js'; -import {GrDiffBuilder} from '../gr-diff-builder/gr-diff-builder.js'; -import {GerritNav} from '../../core/gr-navigation/gr-navigation.js'; -import {DiffSide, rangesEqual} from '../gr-diff/gr-diff-utils.js'; -import {appContext} from '../../../services/app-context.js'; -import {getParentIndex, isMergeParent} from '../../../utils/patch-set-util.js'; -import {sortComments} from '../gr-comment-api/gr-comment-api.js'; +import '../../shared/gr-rest-api-interface/gr-rest-api-interface'; +import '../../shared/gr-comment-thread/gr-comment-thread'; +import '../../shared/gr-js-api-interface/gr-js-api-interface'; +import '../gr-diff/gr-diff'; +import '../gr-syntax-layer/gr-syntax-layer'; +import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners'; +import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin'; +import {PolymerElement} from '@polymer/polymer/polymer-element'; +import {htmlTemplate} from './gr-diff-host_html'; +import {GerritNav} from '../../core/gr-navigation/gr-navigation'; +import {rangesEqual} from '../gr-diff/gr-diff-utils'; +import {appContext} from '../../../services/app-context'; +import { + getParentIndex, + isMergeParent, + isNumber, +} from '../../../utils/patch-set-util'; +import { + Comment, + isDraft, + PatchSetFile, + sortComments, + TwoSidesComments, + UIComment, +} from '../gr-comment-api/gr-comment-api'; +import {customElement, observe, property} from '@polymer/decorators'; +import { + CommitRange, + CoverageRange, + DiffLayer, + DiffLayerListener, +} from '../../../types/types'; +import { + Base64ImageFile, + BlameInfo, + CommentRange, + DiffInfo, + DiffPreferencesInfo, + NumericChangeId, + PatchRange, + PatchSetNum, + RepoName, +} from '../../../types/common'; +import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api'; +import {JsApiService} from '../../shared/gr-js-api-interface/gr-js-api-types'; +import {GrDiff, LineOfInterest} from '../gr-diff/gr-diff'; +import {GrSyntaxLayer} from '../gr-syntax-layer/gr-syntax-layer'; +import { + DiffViewMode, + IgnoreWhitespaceType, + Side, +} from '../../../constants/constants'; +import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces'; +import {FilesWebLinks} from '../gr-patch-range-select/gr-patch-range-select'; +import {LineNumber} from '../gr-diff/gr-diff-line'; +import {GrCommentThread} from '../../shared/gr-comment-thread/gr-comment-thread'; const MSG_EMPTY_BLAME = 'No blame information for this diff.'; @@ -36,12 +77,6 @@ const EVENT_AGAINST_PARENT = 'diff-against-parent'; const EVENT_ZERO_REBASE = 'rebase-percent-zero'; const EVENT_NONZERO_REBASE = 'rebase-percent-nonzero'; -const DiffViewMode = { - SIDE_BY_SIDE: 'SIDE_BY_SIDE', - UNIFIED: 'UNIFIED_DIFF', -}; - -/** @enum {string} */ const TimingLabel = { TOTAL: 'Diff Total Render', CONTENT: 'Diff Content Render', @@ -58,37 +93,58 @@ const SYNTAX_MAX_LINE_LENGTH = 500; // 120 lines is good enough threshold for full-sized window viewport const NUM_OF_LINES_THRESHOLD_FOR_VIEWPORT = 120; -const WHITESPACE_IGNORE_NONE = 'IGNORE_NONE'; +function isImageDiff(diff?: DiffInfo) { + if (!diff) return false; -/** - * @param {Object} diff - * @return {boolean} - */ -function isImageDiff(diff) { - if (!diff) { return false; } - - const isA = diff.meta_a && - diff.meta_a.content_type.startsWith('image/'); - const isB = diff.meta_b && - diff.meta_b.content_type.startsWith('image/'); + const isA = diff.meta_a && diff.meta_a.content_type.startsWith('image/'); + const isB = diff.meta_b && diff.meta_b.content_type.startsWith('image/'); return !!(diff.binary && (isA || isB)); } +interface LineInfo { + beforeNumber?: LineNumber; + afterNumber?: LineNumber; +} + +// TODO(TS): Consolidate this with the CommentThread interface of comment-api. +// What is being used here is just a local object for collecting all the data +// that is needed to create a GrCommentThread component, see +// _createThreadElement(). +interface CommentThread { + comments: UIComment[]; + // In the context of a diff each thread must have a side! + commentSide: Side; + patchNum?: PatchSetNum; + lineNum?: LineNumber; + isOnParent?: boolean; + range?: CommentRange; +} + +export interface GrDiffHost { + $: { + restAPI: RestApiService & Element; + jsAPI: JsApiService & Element; + syntaxLayer: GrSyntaxLayer & Element; + diff: GrDiff; + }; +} + /** * Wrapper around gr-diff. * * Webcomponent fetching diffs and related data from restAPI and passing them - * to the presentational gr-diff for rendering. - * - * @extends PolymerElement + * to the presentational gr-diff for rendering. is a Gerrit + * specific component, while is a re-usable component. */ -class GrDiffHost extends GestureEventListeners( - LegacyElementMixin( - PolymerElement)) { - static get template() { return htmlTemplate; } +@customElement('gr-diff-host') +export class GrDiffHost extends GestureEventListeners( + LegacyElementMixin(PolymerElement) +) { + static get template() { + return htmlTemplate; + } - static get is() { return 'gr-diff-host'; } /** * Fired when the user selects a line. * @@ -107,192 +163,141 @@ class GrDiffHost extends GestureEventListeners( * @event diff-comments-modified */ - static get properties() { - return { - changeNum: String, - noAutoRender: { - type: Boolean, - value: false, - }, - /** @type {?} */ - patchRange: Object, - /** @type {!Gerrit.FileRange} */ - file: Object, - // TODO: deprecate path since that info is included in file - path: String, - prefs: { - type: Object, - }, - projectName: String, - displayLine: { - type: Boolean, - value: false, - }, - isImageDiff: { - type: Boolean, - computed: '_computeIsImageDiff(diff)', - notify: true, - }, - commitRange: Object, - // The return type is FilesWebLinks from gr-patch-range-select. - filesWeblinks: { - type: Object, - value() { - return {}; - }, - notify: true, - }, - hidden: { - type: Boolean, - reflectToAttribute: true, - }, - noRenderOnPrefsChange: { - type: Boolean, - value: false, - }, - comments: { - type: Object, - observer: '_commentsChanged', - }, - lineWrapping: { - type: Boolean, - value: false, - }, - viewMode: { - type: String, - value: DiffViewMode.SIDE_BY_SIDE, - }, + @property({type: Number}) + changeNum?: NumericChangeId; - /** - * Special line number which should not be collapsed into a shared region. - * - * @type {{ - * number: number, - * leftSide: {boolean} - * }|null} - */ - lineOfInterest: Object, + @property({type: Boolean}) + noAutoRender = false; - /** - * If the diff fails to load, show the failure message in the diff rather - * than bubbling the error up to the whole page. This is useful for when - * loading inline diffs because one diff failing need not mark the whole - * page with a failure. - */ - showLoadFailure: Boolean, + @property({type: Object}) + patchRange?: PatchRange; - isBlameLoaded: { - type: Boolean, - notify: true, - computed: '_computeIsBlameLoaded(_blame)', - }, + @property({type: Object}) + file?: PatchSetFile; - _loggedIn: { - type: Boolean, - value: false, - }, + @property({type: String}) + path?: string; - _loading: { - type: Boolean, - value: false, - }, + @property({type: Object}) + prefs?: DiffPreferencesInfo; - /** @type {?string} */ - _errorMessage: { - type: String, - value: null, - }, + @property({type: String}) + projectName?: RepoName; - /** @type {?Object} */ - _baseImage: Object, - /** @type {?Object} */ - _revisionImage: Object, - /** - * This is a DiffInfo object. - */ - diff: { - type: Object, - notify: true, - }, + @property({type: Boolean}) + displayLine = false; - _fetchDiffPromise: { - type: Object, - value: null, - }, + @property({ + type: Boolean, + computed: '_computeIsImageDiff(diff)', + notify: true, + }) + isImageDiff?: boolean; - /** @type {?Object} */ - _blame: { - type: Object, - value: null, - }, + @property({type: Object}) + commitRange?: CommitRange; - /** - * @type {!Array} - */ - _coverageRanges: { - type: Array, - value: () => [], - }, + @property({type: Object, notify: true}) + filesWeblinks: FilesWebLinks | {} = {}; - _loadedWhitespaceLevel: String, + @property({type: Boolean, reflectToAttribute: true}) + hidden = false; - _parentIndex: { - type: Number, - computed: '_computeParentIndex(patchRange.*)', - }, + @property({type: Boolean}) + noRenderOnPrefsChange = false; - _syntaxHighlightingEnabled: { - type: Boolean, - computed: - '_isSyntaxHighlightingEnabled(prefs.*, diff)', - }, + @property({type: Object, observer: '_commentsChanged'}) + comments?: TwoSidesComments; - _layers: { - type: Array, - value: [], - }, - }; - } + @property({type: Boolean}) + lineWrapping = false; - static get observers() { - return [ - '_whitespaceChanged(prefs.ignore_whitespace, _loadedWhitespaceLevel,' + - ' noRenderOnPrefsChange)', - '_syntaxHighlightingChanged(noRenderOnPrefsChange, prefs.*)', - ]; - } + @property({type: String}) + viewMode = DiffViewMode.SIDE_BY_SIDE; - constructor() { - super(); - this.reporting = appContext.reportingService; - } + @property({type: Object}) + lineOfInterest?: LineOfInterest; + + @property({type: Boolean}) + showLoadFailure?: boolean; + + @property({ + type: Boolean, + notify: true, + computed: '_computeIsBlameLoaded(_blame)', + }) + isBlameLoaded?: boolean; + + @property({type: Boolean}) + _loggedIn = false; + + @property({type: Boolean}) + _loading = false; + + @property({type: String}) + _errorMessage: string | null = null; + + @property({type: Object}) + _baseImage: Base64ImageFile | null = null; + + @property({type: Object}) + _revisionImage: Base64ImageFile | null = null; + + @property({type: Object, notify: true}) + diff?: DiffInfo; + + @property({type: Object}) + _fetchDiffPromise: Promise | null = null; + + @property({type: Object}) + _blame: BlameInfo[] | null = null; + + @property({type: Array}) + _coverageRanges: CoverageRange[] = []; + + @property({type: String}) + _loadedWhitespaceLevel?: IgnoreWhitespaceType; + + @property({type: Number, computed: '_computeParentIndex(patchRange.*)'}) + _parentIndex: number | null = null; + + @property({ + type: Boolean, + computed: '_isSyntaxHighlightingEnabled(prefs.*, diff)', + }) + _syntaxHighlightingEnabled?: boolean; + + @property({type: Array}) + _layers: DiffLayer[] = []; + + private readonly reporting = appContext.reportingService; /** @override */ created() { super.created(); this.addEventListener( - // These are named inconsistently for a reason: - // The create-comment event is fired to indicate that we should - // create a comment. - // The comment-* events are just notifying that the comments did already - // change in some way, and that we should update any models we may want - // to keep in sync. - 'create-comment', - e => this._handleCreateComment(e)); - this.addEventListener('comment-discard', - e => this._handleCommentDiscard(e)); - this.addEventListener('comment-update', - e => this._handleCommentUpdate(e)); - this.addEventListener('comment-save', - e => this._handleCommentSave(e)); - this.addEventListener('render-start', - () => this._handleRenderStart()); - this.addEventListener('render-content', - () => this._handleRenderContent()); - this.addEventListener('normalize-range', - event => this._handleNormalizeRange(event)); - this.addEventListener('diff-context-expanded', - event => this._handleDiffContextExpanded(event)); + // These are named inconsistently for a reason: + // The create-comment event is fired to indicate that we should + // create a comment. + // The comment-* events are just notifying that the comments did already + // change in some way, and that we should update any models we may want + // to keep in sync. + 'create-comment', + e => this._handleCreateComment(e) + ); + this.addEventListener('comment-discard', e => + this._handleCommentDiscard(e) + ); + this.addEventListener('comment-update', e => this._handleCommentUpdate(e)); + this.addEventListener('comment-save', e => this._handleCommentSave(e)); + this.addEventListener('render-start', () => this._handleRenderStart()); + this.addEventListener('render-content', () => this._handleRenderContent()); + this.addEventListener('normalize-range', event => + this._handleNormalizeRange(event) + ); + this.addEventListener('diff-context-expanded', event => + this._handleDiffContextExpanded(event) + ); } /** @override */ @@ -318,20 +323,24 @@ class GrDiffHost extends GestureEventListeners( } /** - * @param {boolean=} shouldReportMetric indicate a new Diff Page. This is a + * @param shouldReportMetric indicate a new Diff Page. This is a * signal to report metrics event that started on location change. - * @return {!Promise} - **/ - reload(shouldReportMetric) { + * @return + */ + reload(shouldReportMetric?: boolean) { this.clear(); + if (!this.path) throw new Error('Missing required "path" property.'); + if (!this.changeNum) throw new Error('Missing required "changeNum" prop.'); this._loading = true; this._errorMessage = null; const whitespaceLevel = this._getIgnoreWhitespace(); - const layers = [this.$.syntaxLayer]; + const layers: DiffLayer[] = [this.$.syntaxLayer]; // Get layers from plugins (if any). for (const pluginLayer of this.$.jsAPI.getDiffLayers( - this.path, this.changeNum)) { + this.path, + this.changeNum + )) { layers.push(pluginLayer); } this._layers = layers; @@ -344,19 +353,21 @@ class GrDiffHost extends GestureEventListeners( this._coverageRanges = []; this._getCoverageData(); const diffRequest = this._getDiff() - .then(diff => { - this._loadedWhitespaceLevel = whitespaceLevel; - this._reportDiff(diff); - return diff; - }) - .catch(e => { - this._handleGetDiffError(e); - return null; - }); + .then(diff => { + this._loadedWhitespaceLevel = whitespaceLevel; + this._reportDiff(diff); + return diff; + }) + .catch(e => { + this._handleGetDiffError(e); + return null; + }); const assetRequest = diffRequest.then(diff => { // If the diff is null, then it's failed to load. - if (!diff) { return null; } + if (!diff) { + return null; + } return this._loadDiffAssets(diff); }); @@ -364,105 +375,129 @@ class GrDiffHost extends GestureEventListeners( // Not waiting for coverage ranges intentionally as // plugin loading should not block the content rendering return Promise.all([diffRequest, assetRequest]) - .then(results => { - const diff = results[0]; - if (!diff) { - return Promise.resolve(); - } - this.filesWeblinks = this._getFilesWeblinks(diff); - return new Promise(resolve => { - const callback = event => { - const needsSyntaxHighlighting = event.detail && - event.detail.contentRendered; - if (needsSyntaxHighlighting) { - this.reporting.time(TimingLabel.SYNTAX); - this.$.syntaxLayer.process().finally(() => { - this.reporting.timeEnd(TimingLabel.SYNTAX); - this.reporting.timeEnd(TimingLabel.TOTAL); - resolve(); - }); - } else { + .then(results => { + const diff = results[0]; + if (!diff) { + return Promise.resolve(); + } + this.filesWeblinks = this._getFilesWeblinks(diff); + return new Promise(resolve => { + const callback = (event: CustomEvent) => { + const needsSyntaxHighlighting = + event.detail && event.detail.contentRendered; + if (needsSyntaxHighlighting) { + this.reporting.time(TimingLabel.SYNTAX); + this.$.syntaxLayer.process().finally(() => { + this.reporting.timeEnd(TimingLabel.SYNTAX); this.reporting.timeEnd(TimingLabel.TOTAL); resolve(); - } - this.removeEventListener('render', callback); - if (shouldReportMetric) { - // We report diffViewContentDisplayed only on reload caused - // by params changed - expected only on Diff Page. - this.reporting.diffViewContentDisplayed(); - } - }; - this.addEventListener('render', callback); - this.diff = diff; - }); - }) - .catch(err => { - console.warn('Error encountered loading diff:', err); - }) - .then(() => { this._loading = false; }); + }); + } else { + this.reporting.timeEnd(TimingLabel.TOTAL); + resolve(); + } + this.removeEventListener('render', callback); + if (shouldReportMetric) { + // We report diffViewContentDisplayed only on reload caused + // by params changed - expected only on Diff Page. + this.reporting.diffViewContentDisplayed(); + } + }; + this.addEventListener('render', callback); + this.diff = diff; + }); + }) + .catch(err => { + console.warn('Error encountered loading diff:', err); + }) + .then(() => { + this._loading = false; + }); } clear() { - this.$.jsAPI.disposeDiffLayers(this.path); + if (this.path) this.$.jsAPI.disposeDiffLayers(this.path); this._layers = []; } _getCoverageData() { - const {changeNum, path, patchRange: {basePatchNum, patchNum}} = this; - this.$.jsAPI.getCoverageAnnotationApi(). - then(coverageAnnotationApi => { - if (!coverageAnnotationApi) return; - const provider = coverageAnnotationApi.getCoverageProvider(); - return provider(changeNum, path, basePatchNum, patchNum) - .then(coverageRanges => { - if (!coverageRanges || - changeNum !== this.changeNum || - path !== this.path || - basePatchNum !== this.patchRange.basePatchNum || - patchNum !== this.patchRange.patchNum) { - return; - } + if (!this.changeNum) throw new Error('Missing required "changeNum" prop.'); + if (!this.path) throw new Error('Missing required "path" prop.'); + if (!this.patchRange) throw new Error('Missing required "patchRange".'); + const changeNum = this.changeNum; + const path = this.path; + // Coverage providers do not provide data for EDIT and PARENT patch sets. + const basePatchNum = isNumber(this.patchRange.basePatchNum) + ? this.patchRange.basePatchNum + : undefined; + const patchNum = isNumber(this.patchRange.patchNum) + ? this.patchRange.patchNum + : undefined; + this.$.jsAPI + .getCoverageAnnotationApi() + .then(coverageAnnotationApi => { + if (!coverageAnnotationApi) return; + const provider = coverageAnnotationApi.getCoverageProvider(); + if (!provider) return; + return provider(changeNum, path, basePatchNum, patchNum).then( + coverageRanges => { + if (!this.patchRange) throw new Error('Missing "patchRange".'); + if ( + !coverageRanges || + changeNum !== this.changeNum || + path !== this.path || + basePatchNum !== this.patchRange.basePatchNum || + patchNum !== this.patchRange.patchNum + ) { + return; + } - const existingCoverageRanges = this._coverageRanges; - this._coverageRanges = coverageRanges; + const existingCoverageRanges = this._coverageRanges; + this._coverageRanges = coverageRanges; - // Notify with existing coverage ranges - // in case there is some existing coverage data that needs to be removed - existingCoverageRanges.forEach(range => { - coverageAnnotationApi.notify( - path, - range.code_range.start_line, - range.code_range.end_line, - range.side); - }); + // Notify with existing coverage ranges + // in case there is some existing coverage data that needs to be removed + existingCoverageRanges.forEach(range => { + coverageAnnotationApi.notify( + path, + range.code_range.start_line, + range.code_range.end_line, + range.side + ); + }); - // Notify with new coverage data - coverageRanges.forEach(range => { - coverageAnnotationApi.notify( - path, - range.code_range.start_line, - range.code_range.end_line, - range.side); - }); - }); - }) - .catch(err => { - console.warn('Loading coverage ranges failed: ', err); - }); + // Notify with new coverage data + coverageRanges.forEach(range => { + coverageAnnotationApi.notify( + path, + range.code_range.start_line, + range.code_range.end_line, + range.side + ); + }); + } + ); + }) + .catch(err => { + console.warn('Loading coverage ranges failed: ', err); + }); } - _getFilesWeblinks(diff) { - // The return type is FilesWebLinks from gr-patch-range-select. - if (!this.commitRange) { - return {}; - } + _getFilesWeblinks(diff: DiffInfo) { + if (!this.projectName || !this.commitRange || !this.path) return {}; return { meta_a: GerritNav.getFileWebLinks( - this.projectName, this.commitRange.baseCommit, this.path, - {weblinks: diff && diff.meta_a && diff.meta_a.web_links}), + this.projectName, + this.commitRange.baseCommit, + this.path, + {weblinks: diff && diff.meta_a && diff.meta_a.web_links} + ), meta_b: GerritNav.getFileWebLinks( - this.projectName, this.commitRange.commit, this.path, - {weblinks: diff && diff.meta_b && diff.meta_b.web_links}), + this.projectName, + this.commitRange.commit, + this.path, + {weblinks: diff && diff.meta_b && diff.meta_b.web_links} + ), }; } @@ -472,12 +507,10 @@ class GrDiffHost extends GestureEventListeners( this.$.syntaxLayer.cancel(); } - /** @return {!Array} */ getCursorStops() { return this.$.diff.getCursorStops(); } - /** @return {boolean} */ isRangeSelected() { return this.$.diff.isRangeSelected(); } @@ -492,42 +525,39 @@ class GrDiffHost extends GestureEventListeners( /** * Load and display blame information for the base of the diff. - * - * @return {Promise} A promise that resolves when blame finishes rendering. */ - loadBlame() { - return this.$.restAPI.getBlame(this.changeNum, this.patchRange.patchNum, - this.path, true) - .then(blame => { - if (!blame.length) { - this.dispatchEvent(new CustomEvent('show-alert', { + loadBlame(): Promise { + if (!this.changeNum) throw new Error('Missing required "changeNum" prop.'); + if (!this.patchRange) throw new Error('Missing required "patchRange".'); + if (!this.path) throw new Error('Missing required "path" property.'); + return this.$.restAPI + .getBlame(this.changeNum, this.patchRange.patchNum, this.path, true) + .then(blame => { + if (!blame || !blame.length) { + this.dispatchEvent( + new CustomEvent('show-alert', { detail: {message: MSG_EMPTY_BLAME}, - composed: true, bubbles: true, - })); - return Promise.reject(MSG_EMPTY_BLAME); - } + composed: true, + bubbles: true, + }) + ); + return Promise.reject(MSG_EMPTY_BLAME); + } - this._blame = blame; - }); + this._blame = blame; + return blame; + }); } - /** Unload blame information for the diff. */ clearBlame() { this._blame = null; } - /** - * The thread elements in this diff, in no particular order. - * - * @return {!Array} - */ - getThreadEls() { - return Array.from( - this.$.diff.querySelectorAll('.comment-thread')); + getThreadEls(): GrCommentThread[] { + return Array.from(this.$.diff.querySelectorAll('.comment-thread')); } - /** @param {HTMLElement} el */ - addDraftAtLine(el) { + addDraftAtLine(el: Element) { this.$.diff.addDraftAtLine(el); } @@ -539,27 +569,29 @@ class GrDiffHost extends GestureEventListeners( this.$.diff.expandAllContext(); } - /** @return {!Promise} */ _getLoggedIn() { return this.$.restAPI.getLoggedIn(); } - /** @return {boolean}} */ _canReload() { - return !!this.changeNum && !!this.patchRange && !!this.path && - !this.noAutoRender; + return ( + !!this.changeNum && !!this.patchRange && !!this.path && !this.noAutoRender + ); } // TODO(milutin): Use rest-api with fetchCacheURL instead of this. prefetchDiff() { - if (!!this.changeNum && !!this.patchRange && !!this.path - && this._fetchDiffPromise === null) { + if ( + !!this.changeNum && + !!this.patchRange && + !!this.path && + this._fetchDiffPromise === null + ) { this._fetchDiffPromise = this._getDiff(); } } - /** @return {!Promise} */ - _getDiff() { + _getDiff(): Promise { if (this._fetchDiffPromise !== null) { const fetchDiffPromise = this._fetchDiffPromise; this._fetchDiffPromise = null; @@ -568,25 +600,33 @@ class GrDiffHost extends GestureEventListeners( // Wrap the diff request in a new promise so that the error handler // rejects the promise, allowing the error to be handled in the .catch. return new Promise((resolve, reject) => { - this.$.restAPI.getDiff( + if (!this.changeNum) throw new Error('Missing required "changeNum".'); + if (!this.patchRange) throw new Error('Missing required "patchRange".'); + if (!this.path) throw new Error('Missing required "path" property.'); + this.$.restAPI + .getDiff( this.changeNum, this.patchRange.basePatchNum, this.patchRange.patchNum, this.path, this._getIgnoreWhitespace(), - reject) - .then(resolve); + reject + ) + .then(resolve); }); } - _handleGetDiffError(response) { + _handleGetDiffError(response: Response) { // Loading the diff may respond with 409 if the file is too large. In this // case, use a toast error.. if (response.status === 409) { - this.dispatchEvent(new CustomEvent('server-error', { - detail: {response}, - composed: true, bubbles: true, - })); + this.dispatchEvent( + new CustomEvent('server-error', { + detail: {response}, + composed: true, + bubbles: true, + }) + ); return; } @@ -599,28 +639,33 @@ class GrDiffHost extends GestureEventListeners( return; } - this.dispatchEvent(new CustomEvent('page-error', { - detail: {response}, - composed: true, bubbles: true, - })); + this.dispatchEvent( + new CustomEvent('page-error', { + detail: {response}, + composed: true, + bubbles: true, + }) + ); } /** * Report info about the diff response. */ - _reportDiff(diff) { - if (!diff || !diff.content) { - return; - } + _reportDiff(diff?: DiffInfo) { + if (!diff || !diff.content) return; // Count the delta lines stemming from normal deltas, and from // due_to_rebase deltas. let nonRebaseDelta = 0; let rebaseDelta = 0; diff.content.forEach(chunk => { - if (chunk.ab) { return; } + if (chunk.ab) { + return; + } const deltaSize = Math.max( - chunk.a ? chunk.a.length : 0, chunk.b ? chunk.b.length : 0); + chunk.a ? chunk.a.length : 0, + chunk.b ? chunk.b.length : 0 + ); if (chunk.due_to_rebase) { rebaseDelta += deltaSize; } else { @@ -631,28 +676,28 @@ class GrDiffHost extends GestureEventListeners( // Find the percent of the delta from due_to_rebase chunks rounded to two // digits. Diffs with no delta are considered 0%. const totalDelta = rebaseDelta + nonRebaseDelta; - const percentRebaseDelta = !totalDelta ? 0 : - Math.round(100 * rebaseDelta / totalDelta); + const percentRebaseDelta = !totalDelta + ? 0 + : Math.round((100 * rebaseDelta) / totalDelta); // Report the due_to_rebase percentage in the "diff" category when // applicable. + if (!this.patchRange) throw new Error('Missing required "patchRange".'); if (this.patchRange.basePatchNum === 'PARENT') { this.reporting.reportInteraction(EVENT_AGAINST_PARENT); } else if (percentRebaseDelta === 0) { this.reporting.reportInteraction(EVENT_ZERO_REBASE); } else { - this.reporting.reportInteraction(EVENT_NONZERO_REBASE, - {percentRebaseDelta}); + this.reporting.reportInteraction(EVENT_NONZERO_REBASE, { + percentRebaseDelta, + }); } } - /** - * @param {Object} diff - * @return {!Promise} - */ - _loadDiffAssets(diff) { + _loadDiffAssets(diff?: DiffInfo) { if (isImageDiff(diff)) { - return this._getImages(diff).then(images => { + // diff! is justified, because isImageDiff() returns false otherwise + return this._getImages(diff!).then(images => { this._baseImage = images.baseImage; this._revisionImage = images.revisionImage; }); @@ -663,17 +708,13 @@ class GrDiffHost extends GestureEventListeners( } } - /** - * @param {Object} diff - * @return {boolean} - */ - _computeIsImageDiff(diff) { + _computeIsImageDiff(diff?: DiffInfo) { return isImageDiff(diff); } - _commentsChanged(newComments) { + _commentsChanged(newComments: TwoSidesComments) { const allComments = []; - for (const side of [GrDiffBuilder.Side.LEFT, GrDiffBuilder.Side.RIGHT]) { + for (const side of [Side.LEFT, Side.RIGHT]) { // This is needed by the threading. for (const comment of newComments[side]) { comment.__commentSide = side; @@ -692,11 +733,7 @@ class GrDiffHost extends GestureEventListeners( } } - /** - * @param {!Array} comments - * @return {!Array} Threads for the given comments. - */ - _createThreads(comments) { + _createThreads(comments: UIComment[]): CommentThread[] { const sortedComments = sortComments(comments); const threads = []; for (const comment of sortedComments) { @@ -704,7 +741,8 @@ class GrDiffHost extends GestureEventListeners( // 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)); + thread.comments.some(c => c.id === comment.in_reply_to) + ); if (thread) { thread.comments.push(comment); continue; @@ -712,12 +750,11 @@ class GrDiffHost extends GestureEventListeners( } // Otherwise, this comment starts its own thread. - const newThread = { - start_datetime: comment.updated, + if (!comment.__commentSide) throw new Error('Missing "__commentSide".'); + const newThread: CommentThread = { comments: [comment], commentSide: comment.__commentSide, patchNum: comment.patch_set, - rootId: comment.id || comment.__draftID, lineNum: comment.line, isOnParent: comment.side === 'PARENT', }; @@ -729,28 +766,29 @@ class GrDiffHost extends GestureEventListeners( return threads; } - /** - * @param {Object} blame - * @return {boolean} - */ - _computeIsBlameLoaded(blame) { + _computeIsBlameLoaded(blame: BlameInfo[] | null) { return !!blame; } - /** - * @param {Object} diff - * @return {!Promise} - */ - _getImages(diff) { - return this.$.restAPI.getImagesForDiff(this.changeNum, diff, - this.patchRange); + _getImages(diff: DiffInfo) { + if (!this.changeNum) throw new Error('Missing required "changeNum" prop.'); + if (!this.patchRange) throw new Error('Missing required "patchRange".'); + return this.$.restAPI.getImagesForDiff( + this.changeNum, + diff, + this.patchRange + ); } - /** @param {CustomEvent} e */ - _handleCreateComment(e) { + _handleCreateComment(e: CustomEvent) { const {lineNum, side, patchNum, isOnParent, range} = e.detail; - const threadEl = this._getOrCreateThread(patchNum, lineNum, side, range, - isOnParent); + const threadEl = this._getOrCreateThread( + patchNum, + lineNum, + side, + range, + isOnParent + ); threadEl.addOrEditDraft(lineNum, range); this.reporting.recordDraftInteraction(); @@ -759,15 +797,14 @@ class GrDiffHost extends GestureEventListeners( /** * Gets or creates a comment thread at a given location. * May provide a range, to get/create a range comment. - * - * @param {string} patchNum - * @param {?number} lineNum - * @param {string} commentSide - * @param {Gerrit.Range|undefined} range - * @param {boolean} isOnParent - * @return {!Object} */ - _getOrCreateThread(patchNum, lineNum, commentSide, range, isOnParent) { + _getOrCreateThread( + patchNum: PatchSetNum, + lineNum: LineNumber | undefined, + commentSide: Side, + range?: CommentRange, + isOnParent?: boolean + ): GrCommentThread { let threadEl = this._getThreadEl(lineNum, commentSide, range); if (!threadEl) { threadEl = this._createThreadElement({ @@ -783,18 +820,18 @@ class GrDiffHost extends GestureEventListeners( return threadEl; } - _attachThreadElement(threadEl) { + _attachThreadElement(threadEl: Element) { this.$.diff.appendChild(threadEl); } _clearThreads() { for (const threadEl of this.getThreadEls()) { const parent = threadEl.parentNode; - parent.removeChild(threadEl); + if (parent) parent.removeChild(threadEl); } } - _createThreadElement(thread) { + _createThreadElement(thread: CommentThread) { const threadEl = document.createElement('gr-comment-thread'); threadEl.className = 'comment-thread'; threadEl.setAttribute('slot', `${thread.commentSide}-${thread.lineNum}`); @@ -804,9 +841,12 @@ class GrDiffHost extends GestureEventListeners( threadEl.parentIndex = this._parentIndex; // Use path before renmaing when comment added on the left when comparing // two patch sets (not against base) - if (this.file && this.file.basePath - && thread.commentSide === GrDiffBuilder.Side.LEFT - && !thread.isOnParent) { + if ( + this.file && + this.file.basePath && + thread.commentSide === Side.LEFT && + !thread.isOnParent + ) { threadEl.path = this.file.basePath; } else { threadEl.path = this.path; @@ -814,20 +854,14 @@ class GrDiffHost extends GestureEventListeners( threadEl.changeNum = this.changeNum; threadEl.patchNum = thread.patchNum; threadEl.showPatchset = false; - threadEl.lineNum = thread.lineNum; - const rootIdChangedListener = changeEvent => { - thread.rootId = changeEvent.detail.value; - }; - threadEl.addEventListener('root-id-changed', rootIdChangedListener); + // GrCommentThread does not understand 'FILE', but requires undefined. + threadEl.lineNum = thread.lineNum !== 'FILE' ? thread.lineNum : undefined; threadEl.projectName = this.projectName; threadEl.range = thread.range; - const threadDiscardListener = e => { - const threadEl = /** @type {!Node} */ (e.currentTarget); - + const threadDiscardListener = (e: Event) => { + const threadEl = e.currentTarget as Element; const parent = threadEl.parentNode; - parent.removeChild(threadEl); - - threadEl.removeEventListener('root-id-changed', rootIdChangedListener); + if (parent) parent.removeChild(threadEl); threadEl.removeEventListener('thread-discard', threadDiscardListener); }; threadEl.addEventListener('thread-discard', threadDiscardListener); @@ -837,132 +871,132 @@ class GrDiffHost extends GestureEventListeners( /** * Gets a comment thread element at a given location. * May provide a range, to get a range comment. - * - * @param {?number} lineNum - * @param {string} commentSide - * @param {!Gerrit.Range=} range - * @return {?Node} */ - _getThreadEl(lineNum, commentSide, range = undefined) { - let line; - if (commentSide === GrDiffBuilder.Side.LEFT) { + _getThreadEl( + lineNum: LineNumber | undefined, + commentSide: Side, + range?: CommentRange + ): GrCommentThread | null { + let line: LineInfo; + if (commentSide === Side.LEFT) { line = {beforeNumber: lineNum}; - } else if (commentSide === GrDiffBuilder.Side.RIGHT) { + } else if (commentSide === Side.RIGHT) { line = {afterNumber: lineNum}; } else { throw new Error(`Unknown side: ${commentSide}`); } - function matchesRange(threadEl) { - const threadRange = /** @type {!Gerrit.Range} */( - JSON.parse(threadEl.getAttribute('range'))); + function matchesRange(threadEl: GrCommentThread) { + const rangeAtt = threadEl.getAttribute('range'); + const threadRange = rangeAtt ? JSON.parse(rangeAtt) : undefined; return rangesEqual(threadRange, range); } const filteredThreadEls = this._filterThreadElsForLocation( - this.getThreadEls(), line, commentSide).filter(matchesRange); + this.getThreadEls(), + line, + commentSide + ).filter(matchesRange); return filteredThreadEls.length ? filteredThreadEls[0] : null; } - /** - * @param {!Array} threadEls - * @param {!{beforeNumber: (number|string|undefined|null), - * afterNumber: (number|string|undefined|null)}} - * lineInfo - * @param {!DiffSide=} side The side (LEFT, RIGHT) for - * which to return the threads. - * @return {!Array} The thread elements matching the given - * location. - */ - _filterThreadElsForLocation(threadEls, lineInfo, side) { - function matchesLeftLine(threadEl) { - return threadEl.getAttribute('comment-side') == - DiffSide.LEFT && - threadEl.getAttribute('line-num') == lineInfo.beforeNumber; + _filterThreadElsForLocation( + threadEls: GrCommentThread[], + lineInfo: LineInfo, + side: Side + ) { + function matchesLeftLine(threadEl: GrCommentThread) { + return ( + threadEl.getAttribute('comment-side') === Side.LEFT && + threadEl.getAttribute('line-num') === String(lineInfo.beforeNumber) + ); } - function matchesRightLine(threadEl) { - return threadEl.getAttribute('comment-side') == - DiffSide.RIGHT && - threadEl.getAttribute('line-num') == lineInfo.afterNumber; + function matchesRightLine(threadEl: GrCommentThread) { + return ( + threadEl.getAttribute('comment-side') === Side.RIGHT && + threadEl.getAttribute('line-num') === String(lineInfo.afterNumber) + ); } - function matchesFileComment(threadEl) { - return threadEl.getAttribute('comment-side') == side && - // line/range comments have 1-based line set, if line is falsy it's - // a file comment - !threadEl.getAttribute('line-num'); + function matchesFileComment(threadEl: GrCommentThread) { + return ( + threadEl.getAttribute('comment-side') === side && + // line/range comments have 1-based line set, if line is falsy it's + // a file comment + !threadEl.getAttribute('line-num') + ); } // Select the appropriate matchers for the desired side and line // If side is BOTH, we want both the left and right matcher. - const matchers = []; - if (side !== DiffSide.RIGHT) { + const matchers: ((thread: GrCommentThread) => boolean)[] = []; + if (side !== Side.RIGHT) { matchers.push(matchesLeftLine); } - if (side !== DiffSide.LEFT) { + if (side !== Side.LEFT) { matchers.push(matchesRightLine); } - if (lineInfo.afterNumber === 'FILE' || - lineInfo.beforeNumber === 'FILE') { + if (lineInfo.afterNumber === 'FILE' || lineInfo.beforeNumber === 'FILE') { matchers.push(matchesFileComment); } return threadEls.filter(threadEl => - matchers.some(matcher => matcher(threadEl))); + matchers.some(matcher => matcher(threadEl)) + ); } - _getIgnoreWhitespace() { + _getIgnoreWhitespace(): IgnoreWhitespaceType { if (!this.prefs || !this.prefs.ignore_whitespace) { - return WHITESPACE_IGNORE_NONE; + return IgnoreWhitespaceType.IGNORE_NONE; } return this.prefs.ignore_whitespace; } + @observe( + 'prefs.ignore_whitespace', + '_loadedWhitespaceLevel', + 'noRenderOnPrefsChange' + ) _whitespaceChanged( - preferredWhitespaceLevel, loadedWhitespaceLevel, - noRenderOnPrefsChange) { - // Polymer 2: check for undefined - if ([ - preferredWhitespaceLevel, - loadedWhitespaceLevel, - noRenderOnPrefsChange, - ].includes(undefined)) { - return; - } + preferredWhitespaceLevel?: IgnoreWhitespaceType, + loadedWhitespaceLevel?: IgnoreWhitespaceType, + noRenderOnPrefsChange?: boolean + ) { + if (preferredWhitespaceLevel === undefined) return; + if (loadedWhitespaceLevel === undefined) return; + if (noRenderOnPrefsChange === undefined) return; this._fetchDiffPromise = null; - if (preferredWhitespaceLevel !== loadedWhitespaceLevel && - !noRenderOnPrefsChange) { + if ( + preferredWhitespaceLevel !== loadedWhitespaceLevel && + !noRenderOnPrefsChange + ) { this.reload(); } } - _syntaxHighlightingChanged(noRenderOnPrefsChange, prefsChangeRecord) { - // Polymer 2: check for undefined - if ([ - noRenderOnPrefsChange, - prefsChangeRecord, - ].includes(undefined)) { - return; - } + @observe('noRenderOnPrefsChange', 'prefs.*') + _syntaxHighlightingChanged( + noRenderOnPrefsChange?: boolean, + prefsChangeRecord?: PolymerDeepPropertyChange< + DiffPreferencesInfo, + DiffPreferencesInfo + > + ) { + if (noRenderOnPrefsChange === undefined) return; + if (prefsChangeRecord === undefined) return; + if (prefsChangeRecord.path !== 'prefs.syntax_highlighting') return; - if (prefsChangeRecord.path !== 'prefs.syntax_highlighting') { - return; - } - - if (!noRenderOnPrefsChange) { - this.reload(); - } + if (!noRenderOnPrefsChange) this.reload(); } - /** - * @param {Object} patchRangeRecord - * @return {number|null} - */ - _computeParentIndex(patchRangeRecord) { + _computeParentIndex( + patchRangeRecord: PolymerDeepPropertyChange + ) { if (!patchRangeRecord.base) return null; - return isMergeParent(patchRangeRecord.base.basePatchNum) ? - getParentIndex(patchRangeRecord.base.basePatchNum) : null; + return isMergeParent(patchRangeRecord.base.basePatchNum) + ? getParentIndex(patchRangeRecord.base.basePatchNum) + : null; } - _handleCommentSave(e) { + _handleCommentSave(e: CustomEvent) { const comment = e.detail.comment; const side = e.detail.comment.__commentSide; const idx = this._findDraftIndex(comment, side); @@ -970,44 +1004,41 @@ class GrDiffHost extends GestureEventListeners( this._handleCommentSaveOrDiscard(); } - _handleCommentDiscard(e) { + _handleCommentDiscard(e: CustomEvent) { const comment = e.detail.comment; this._removeComment(comment); this._handleCommentSaveOrDiscard(); } - /** - * Closure annotation for Polymer.prototype.push is off. Submitted PR: - * https://github.com/Polymer/polymer/pull/4776 - * but for not suppressing annotations. - * - * @suppress {checkTypes} - */ - _handleCommentUpdate(e) { + _handleCommentUpdate(e: CustomEvent) { const comment = e.detail.comment; const side = e.detail.comment.__commentSide; let idx = this._findCommentIndex(comment, side); if (idx === -1) { idx = this._findDraftIndex(comment, side); } - if (idx !== -1) { // Update draft or comment. + if (idx !== -1) { + // Update draft or comment. this.set(['comments', side, idx], comment); - } else { // Create new draft. + } else { + // Create new draft. this.push(['comments', side], comment); } } _handleCommentSaveOrDiscard() { - this.dispatchEvent(new CustomEvent( - 'diff-comments-modified', {bubbles: true, composed: true})); + this.dispatchEvent( + new CustomEvent('diff-comments-modified', {bubbles: true, composed: true}) + ); } - _removeComment(comment) { + _removeComment(comment: UIComment) { const side = comment.__commentSide; + if (!side) throw new Error('Missing required "side" in comment.'); this._removeCommentFromSide(comment, side); } - _removeCommentFromSide(comment, side) { + _removeCommentFromSide(comment: Comment, side: Side) { let idx = this._findCommentIndex(comment, side); if (idx === -1) { idx = this._findDraftIndex(comment, side); @@ -1017,50 +1048,64 @@ class GrDiffHost extends GestureEventListeners( } } - /** @return {number} */ - _findCommentIndex(comment, side) { - if (!comment.id || !this.comments[side]) { + _findCommentIndex(comment: Comment, side: Side) { + if (!comment.id || !this.comments || !this.comments[side]) { return -1; } return this.comments[side].findIndex(item => item.id === comment.id); } - /** @return {number} */ - _findDraftIndex(comment, side) { - if (!comment.__draftID || !this.comments[side]) { + _findDraftIndex(comment: Comment, side: Side) { + if ( + !isDraft(comment) || + !comment.__draftID || + !this.comments || + !this.comments[side] + ) { return -1; } return this.comments[side].findIndex( - item => item.__draftID === comment.__draftID); + item => isDraft(item) && item.__draftID === comment.__draftID + ); } - _isSyntaxHighlightingEnabled(preferenceChangeRecord, diff) { - if (!preferenceChangeRecord || - !preferenceChangeRecord.base || - !preferenceChangeRecord.base.syntax_highlighting || - !diff) { + _isSyntaxHighlightingEnabled( + preferenceChangeRecord?: PolymerDeepPropertyChange< + DiffPreferencesInfo, + DiffPreferencesInfo + >, + diff?: DiffInfo + ) { + if ( + !preferenceChangeRecord || + !preferenceChangeRecord.base || + !preferenceChangeRecord.base.syntax_highlighting || + !diff + ) { return false; } - return !this._anyLineTooLong(diff) && - this.$.diff.getDiffLength(diff) <= SYNTAX_MAX_DIFF_LENGTH; + return ( + !this._anyLineTooLong(diff) && + this.$.diff.getDiffLength(diff) <= SYNTAX_MAX_DIFF_LENGTH + ); } /** - * @return {boolean} whether any of the lines in diff are longer + * @return whether any of the lines in diff are longer * than SYNTAX_MAX_LINE_LENGTH. */ - _anyLineTooLong(diff) { + _anyLineTooLong(diff?: DiffInfo) { if (!diff) return false; return diff.content.some(section => { - const lines = section.ab ? - section.ab : - (section.a || []).concat(section.b || []); + const lines = section.ab + ? section.ab + : (section.a || []).concat(section.b || []); return lines.some(line => line.length >= SYNTAX_MAX_LINE_LENGTH); }); } _listenToViewportRender() { - const renderUpdateListener = start => { + const renderUpdateListener: DiffLayerListener = start => { if (start > NUM_OF_LINES_THRESHOLD_FOR_VIEWPORT) { this.reporting.diffViewDisplayed(); this.$.syntaxLayer.removeListener(renderUpdateListener); @@ -1079,31 +1124,31 @@ class GrDiffHost extends GestureEventListeners( this.reporting.timeEnd(TimingLabel.CONTENT); } - _handleNormalizeRange(event) { - this.reporting.reportInteraction('normalize-range', - { - side: event.detail.side, - lineNum: event.detail.lineNum, - }); + _handleNormalizeRange(event: CustomEvent) { + this.reporting.reportInteraction('normalize-range', { + side: event.detail.side, + lineNum: event.detail.lineNum, + }); } - _handleDiffContextExpanded(event) { - this.reporting.reportInteraction( - 'diff-context-expanded', {numLines: event.detail.numLines} - ); + _handleDiffContextExpanded(event: CustomEvent) { + this.reporting.reportInteraction('diff-context-expanded', { + numLines: event.detail.numLines, + }); } /** * Find the last chunk for the given side. * - * @param {!Object} diff - * @param {boolean} leftSide true if checking the base of the diff, - * false if testing the revision. - * @return {Object|null} returns the chunk object or null if there was - * no chunk for that side. + * @param leftSide true if checking the base of the diff, + * false if testing the revision. + * @return returns the chunk object or null if there was + * no chunk for that side. */ - _lastChunkForSide(diff, leftSide) { - if (!diff.content.length) { return null; } + _lastChunkForSide(diff: DiffInfo | undefined, leftSide: boolean) { + if (!diff?.content.length) { + return null; + } let chunkIndex = diff.content.length; let chunk; @@ -1113,19 +1158,20 @@ class GrDiffHost extends GestureEventListeners( chunkIndex--; chunk = diff.content[chunkIndex]; } while ( - // We haven't reached the beginning. + // We haven't reached the beginning. chunkIndex >= 0 && - - // The chunk doesn't have both sides. - !chunk.ab && - - // The chunk doesn't have the given side. - ((leftSide && (!chunk.a || !chunk.a.length)) || - (!leftSide && (!chunk.b || !chunk.b.length)))); + // The chunk doesn't have both sides. + !chunk.ab && + // The chunk doesn't have the given side. + ((leftSide && (!chunk.a || !chunk.a.length)) || + (!leftSide && (!chunk.b || !chunk.b.length))) + ); // If we reached the beginning of the diff and failed to find a chunk // with the given side, return null. - if (chunkIndex === -1) { return null; } + if (chunkIndex === -1) { + return null; + } return chunk; } @@ -1133,32 +1179,50 @@ class GrDiffHost extends GestureEventListeners( /** * Check whether the specified side of the diff has a trailing newline. * - * @param {!Object} diff - * @param {boolean} leftSide true if checking the base of the diff, - * false if testing the revision. - * @return {boolean|null} Return true if the side has a trailing newline. - * Return false if it doesn't. Return null if not applicable (for - * example, if the diff has no content on the specified side). + * @param leftSide true if checking the base of the diff, + * false if testing the revision. + * @return Return true if the side has a trailing newline. + * Return false if it doesn't. Return null if not applicable (for + * example, if the diff has no content on the specified side). */ - _hasTrailingNewlines(diff, leftSide) { + _hasTrailingNewlines(diff: DiffInfo | undefined, leftSide: boolean) { const chunk = this._lastChunkForSide(diff, leftSide); - if (!chunk) { return null; } + if (!chunk) return null; let lines; if (chunk.ab) { lines = chunk.ab; } else { lines = leftSide ? chunk.a : chunk.b; } + if (!lines) return null; return lines[lines.length - 1] === ''; } - _showNewlineWarningLeft(diff) { + _showNewlineWarningLeft(diff?: DiffInfo) { return this._hasTrailingNewlines(diff, true) === false; } - _showNewlineWarningRight(diff) { + _showNewlineWarningRight(diff?: DiffInfo) { return this._hasTrailingNewlines(diff, false) === false; } } -customElements.define(GrDiffHost.is, GrDiffHost); +declare global { + interface HTMLElementTagNameMap { + 'gr-diff-host': GrDiffHost; + } +} + +// TODO(TS): Be more specific than CustomEvent, which has detail:any. +declare global { + interface HTMLElementEventMap { + render: CustomEvent; + 'normalize-range': CustomEvent; + 'diff-context-expanded': CustomEvent; + 'create-comment': CustomEvent; + 'comment-discard': CustomEvent; + 'comment-update': CustomEvent; + 'comment-save': CustomEvent; + 'root-id-changed': CustomEvent; + } +} diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js index 11539b12ca..ac96ba26ae 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js +++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js @@ -20,8 +20,8 @@ import './gr-diff-host.js'; import {GrDiffBuilderImage} from '../gr-diff-builder/gr-diff-builder-image.js'; import {GerritNav} from '../../core/gr-navigation/gr-navigation.js'; import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js'; -import {DiffSide} from '../gr-diff/gr-diff-utils.js'; import {sortComments} from '../gr-comment-api/gr-comment-api.js'; +import {Side} from '../../../constants/constants.js'; const basicFixture = fixtureFromElement('gr-diff-host'); @@ -36,6 +36,8 @@ suite('gr-diff-host tests', () => { async getLoggedIn() { return getLoggedIn; }, }); element = basicFixture.instantiate(); + element.changeNum = 123; + element.path = 'some/path'; sinon.stub(element.reporting, 'time'); sinon.stub(element.reporting, 'timeEnd'); }); @@ -47,6 +49,8 @@ suite('gr-diff-host tests', () => { getDiffLayers() { return pluginLayers; }, }); element = basicFixture.instantiate(); + element.changeNum = 123; + element.path = 'some/path'; }); test('plugin layers requested', () => { element.patchRange = {}; @@ -172,7 +176,6 @@ suite('gr-diff-host tests', () => { ], }; - element._removeComment({}); // Using JSON.stringify because Safari 9.1 (11601.5.17.1) doesn’t seem // to believe that one object deepEquals another even when they do :-/. assert.equal(JSON.stringify(element.comments), JSON.stringify({ @@ -249,12 +252,20 @@ suite('gr-diff-host tests', () => { }); test('thread-discard handling', () => { - const threads = [ - {comments: [{id: 4711}]}, - {comments: [{id: 42}]}, - ]; + const threads = element._createThreads([ + { + id: 4711, + __commentSide: 'left', + updated: '2015-12-20 15:01:20.396000000', + }, + { + id: 42, + __commentSide: 'left', + updated: '2017-12-20 15:01:20.396000000', + }, + ]); element._parentIndex = 1; - element.changeNum = '2'; + element.changeNum = 2; element.path = 'some/path'; element.projectName = 'Some project'; const threadEls = threads.map( @@ -268,8 +279,8 @@ suite('gr-diff-host tests', () => { return threadEl; }); assert.equal(threadEls.length, 2); - assert.equal(threadEls[0].rootId, 4711); - assert.equal(threadEls[1].rootId, 42); + assert.equal(threadEls[0].comments[0].id, 4711); + assert.equal(threadEls[1].comments[0].id, 42); for (const threadEl of threadEls) { element.appendChild(threadEl); } @@ -279,7 +290,7 @@ suite('gr-diff-host tests', () => { const attachedThreads = element.queryAllEffectiveChildren( 'gr-comment-thread'); assert.equal(attachedThreads.length, 1); - assert.equal(attachedThreads[0].rootId, 42); + assert.equal(attachedThreads[0].comments[0].id, 42); }); suite('render reporting', () => { @@ -393,6 +404,8 @@ suite('gr-diff-host tests', () => { setup(() => { getLoggedIn = false; element = basicFixture.instantiate(); + element.changeNum = 123; + element.path = 'some/path'; }); test('reload() loads files weblinks', () => { @@ -852,6 +865,8 @@ suite('gr-diff-host tests', () => { suite('blame', () => { setup(() => { element = basicFixture.instantiate(); + element.changeNum = 123; + element.path = 'some/path'; }); test('clearBlame', () => { @@ -933,9 +948,8 @@ suite('gr-diff-host tests', () => { }); test('passes in changeNum', () => { - const value = '12345'; - element.changeNum = value; - assert.equal(element.$.diff.changeNum, value); + element.changeNum = 12345; + assert.equal(element.$.diff.changeNum, 12345); }); test('passes in noAutoRender', () => { @@ -963,9 +977,8 @@ suite('gr-diff-host tests', () => { }); test('passes in changeNum', () => { - const value = '12345'; - element.changeNum = value; - assert.equal(element.$.diff.changeNum, value); + element.changeNum = 12345; + assert.equal(element.$.diff.changeNum, 12345); }); test('passes in projectName', () => { @@ -1016,6 +1029,7 @@ suite('gr-diff-host tests', () => { setup(() => { element = basicFixture.instantiate(); + element.changeNum = 123; element.path = 'file.txt'; element.patchRange = {basePatchNum: 1}; reportStub = sinon.stub(element.reporting, 'reportInteraction'); @@ -1167,23 +1181,17 @@ suite('gr-diff-host tests', () => { assert.equal(actualThreads.length, 2); - assert.equal( - actualThreads[0].start_datetime, '2015-12-23 15:00:20.396000000'); assert.equal(actualThreads[0].commentSide, 'left'); assert.equal(actualThreads[0].comments.length, 2); assert.deepEqual(actualThreads[0].comments[0], comments[0]); assert.deepEqual(actualThreads[0].comments[1], comments[1]); assert.equal(actualThreads[0].patchNum, undefined); - assert.equal(actualThreads[0].rootId, 'sallys_confession'); assert.equal(actualThreads[0].lineNum, 1); - assert.equal( - actualThreads[1].start_datetime, '2015-12-20 15:01:20.396000000'); assert.equal(actualThreads[1].commentSide, 'left'); assert.equal(actualThreads[1].comments.length, 1); assert.deepEqual(actualThreads[1].comments[0], comments[2]); assert.equal(actualThreads[1].patchNum, undefined); - assert.equal(actualThreads[1].rootId, 'new_draft'); assert.equal(actualThreads[1].lineNum, undefined); }); @@ -1205,7 +1213,6 @@ suite('gr-diff-host tests', () => { const expectedThreads = [ { - start_datetime: '2015-12-24 15:00:10.396000000', commentSide: 'left', comments: [{ id: 'betsys_confession', @@ -1222,7 +1229,6 @@ suite('gr-diff-host tests', () => { line: 1, }], patchNum: 5, - rootId: 'betsys_confession', range: { start_line: 1, start_character: 1, @@ -1264,14 +1270,12 @@ suite('gr-diff-host tests', () => { id: 'sallys_confession', message: 'i like you, jack', updated: '2015-12-23 15:00:20.396000000', - // line: 1, - // __commentSide: 'left', + __commentSide: 'left', }, { id: 'jacks_reply', message: 'i like you, too', updated: '2015-12-24 15:01:20.396000000', - // __commentSide: 'left', - // line: 1, + __commentSide: 'left', in_reply_to: 'sallys_confession', }, ]; @@ -1375,9 +1379,9 @@ suite('gr-diff-host tests', () => { const threads = []; assert.deepEqual(element._filterThreadElsForLocation(threads, line), []); assert.deepEqual(element._filterThreadElsForLocation(threads, line, - DiffSide.LEFT), []); + Side.LEFT), []); assert.deepEqual(element._filterThreadElsForLocation(threads, line, - DiffSide.RIGHT), []); + Side.RIGHT), []); }); test('_filterThreadElsForLocation for line comments', () => { @@ -1403,9 +1407,9 @@ suite('gr-diff-host tests', () => { assert.deepEqual(element._filterThreadElsForLocation(threadEls, line), [l3, r5]); assert.deepEqual(element._filterThreadElsForLocation(threadEls, line, - DiffSide.LEFT), [l3]); + Side.LEFT), [l3]); assert.deepEqual(element._filterThreadElsForLocation(threadEls, line, - DiffSide.RIGHT), [r5]); + Side.RIGHT), [r5]); }); test('_filterThreadElsForLocation for file comments', () => { @@ -1423,11 +1427,11 @@ suite('gr-diff-host tests', () => { assert.deepEqual(element._filterThreadElsForLocation(threadEls, line), [l, r]); assert.deepEqual(element._filterThreadElsForLocation(threadEls, line, - DiffSide.BOTH), [l, r]); + Side.BOTH), [l, r]); assert.deepEqual(element._filterThreadElsForLocation(threadEls, line, - DiffSide.LEFT), [l]); + Side.LEFT), [l]); assert.deepEqual(element._filterThreadElsForLocation(threadEls, line, - DiffSide.RIGHT), [r]); + Side.RIGHT), [r]); }); suite('syntax layer with syntax_highlighting on', () => { @@ -1441,6 +1445,8 @@ suite('gr-diff-host tests', () => { }; element.patchRange = {}; element.prefs = prefs; + element.changeNum = 123; + element.path = 'some/path'; }); test('gr-diff-host provides syntax highlighting layer to gr-diff', () => { @@ -1551,6 +1557,8 @@ suite('gr-diff-host tests', () => { }, }); element = basicFixture.instantiate(); + element.changeNum = 123; + element.path = 'some/path'; const prefs = { line_length: 10, show_tabs: true, diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts index a85af34aaa..39e12cad18 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts @@ -31,7 +31,7 @@ import { import {CancelablePromise, util} from '../../../scripts/util'; import {customElement, property} from '@polymer/decorators'; import {DiffContent} from '../../../types/common'; -import {DiffSide} from '../gr-diff/gr-diff-utils'; +import {Side} from '../../../constants/constants'; const WHOLE_FILE = -1; @@ -567,8 +567,8 @@ export class GrDiffProcessor extends GestureEventListeners( for (let i = 0; i < numLines; i++) { // If this line should not be collapsed. if ( - this.keyLocations[DiffSide.LEFT][leftOffset + i] || - this.keyLocations[DiffSide.RIGHT][rightOffset + i] + this.keyLocations[Side.LEFT][leftOffset + i] || + this.keyLocations[Side.RIGHT][rightOffset + i] ) { // If any lines have been accumulated into the chunk leading up to // this non-collapse line, then add them as a chunk and start a new diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-utils.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-utils.ts index 0aa42c3fc3..8984dc83ac 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-utils.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-utils.ts @@ -18,17 +18,12 @@ import {CommentRange} from '../../../types/common'; import {FILE, LineNumber} from './gr-diff-line'; -export enum DiffSide { - LEFT = 'left', - RIGHT = 'right', -} - /** * Compare two ranges. Either argument may be falsy, but will only return * true if both are falsy or if neither are falsy and have the same position * values. */ -export function rangesEqual(a: CommentRange, b: CommentRange): boolean { +export function rangesEqual(a?: CommentRange, b?: CommentRange): boolean { if (!a && !b) { return true; } diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts index 8ac47a2954..159c056a94 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts @@ -27,7 +27,7 @@ import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-l import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin'; import {htmlTemplate} from './gr-diff_html'; import {FILE, LineNumber} from './gr-diff-line'; -import {DiffSide, getLineNumber, rangesEqual} from './gr-diff-utils'; +import {getLineNumber, rangesEqual} from './gr-diff-utils'; import {getHiddenScroll} from '../../../scripts/hiddenscroll'; import {isMergeParent, patchNumEquals} from '../../../utils/patch-set-util'; import {customElement, observe, property} from '@polymer/decorators'; @@ -95,7 +95,7 @@ const COMMIT_MSG_LINE_LENGTH = 72; const RENDER_DIFF_TABLE_DEBOUNCE_NAME = 'renderDiffTable'; -interface LineOfInterest { +export interface LineOfInterest { number: number; leftSide: boolean; } @@ -534,7 +534,7 @@ export class GrDiff extends GestureEventListeners( this.dispatchEvent( new CustomEvent('line-selected', { detail: { - side: el.classList.contains('left') ? DiffSide.LEFT : DiffSide.RIGHT, + side: el.classList.contains('left') ? Side.LEFT : Side.RIGHT, number: el.getAttribute('data-value'), path: this.path, }, @@ -614,7 +614,7 @@ export class GrDiff extends GestureEventListeners( ); return false; } - const patchNum = el.classList.contains(DiffSide.LEFT) + const patchNum = el.classList.contains(Side.LEFT) ? this.patchRange.basePatchNum : this.patchRange.patchNum; @@ -717,7 +717,7 @@ export class GrDiff extends GestureEventListeners( let patchNum = this.patchRange.patchNum; if ( - (lineEl.classList.contains(DiffSide.LEFT) || + (lineEl.classList.contains(Side.LEFT) || contentEl.classList.contains('remove')) && this.patchRange.basePatchNum !== 'PARENT' && !isMergeParent(this.patchRange.basePatchNum) @@ -730,7 +730,7 @@ export class GrDiff extends GestureEventListeners( _getIsParentCommentByLineAndContent(lineEl: Element, contentEl: Element) { if (!this.patchRange) throw Error('patch range not set'); return ( - (lineEl.classList.contains(DiffSide.LEFT) || + (lineEl.classList.contains(Side.LEFT) || contentEl.classList.contains('remove')) && (this.patchRange.basePatchNum === 'PARENT' || isMergeParent(this.patchRange.basePatchNum)) @@ -740,7 +740,7 @@ export class GrDiff extends GestureEventListeners( _getCommentSideByLineAndContent(lineEl: Element, contentEl: Element): Side { let side = Side.RIGHT; if ( - lineEl.classList.contains(DiffSide.LEFT) || + lineEl.classList.contains(Side.LEFT) || contentEl.classList.contains('remove') ) { side = Side.LEFT; diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts index b6e188502a..2b997fd2c0 100644 --- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts +++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts @@ -51,7 +51,7 @@ import { DropDownValueChangeEvent, GrDropdownList, } from '../../shared/gr-dropdown-list/gr-dropdown-list'; -import {WebLink} from '../../core/gr-navigation/gr-navigation'; +import {GeneratedWebLink} from '../../core/gr-navigation/gr-navigation'; // Maximum length for patch set descriptions. const PATCH_DESC_MAX_LENGTH = 500; @@ -63,9 +63,9 @@ export interface PatchRangeChangeDetail { export type PatchRangeChangeEvent = CustomEvent; -interface FilesWebLinks { - meta_a: WebLink[]; - meta_b: WebLink[]; +export interface FilesWebLinks { + meta_a: GeneratedWebLink[]; + meta_b: GeneratedWebLink[]; } export interface GrPatchRangeSelect { diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.js b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.js index 2d0924a890..eb9f47dc50 100644 --- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.js +++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.js @@ -25,6 +25,7 @@ import {RevisionInfo} from '../../shared/revision-info/revision-info.js'; import {createCommentApiMockWithTemplateElement} from '../../../test/mocks/comment-api'; import {html} from '@polymer/polymer/lib/utils/html-tag.js'; import {SPECIAL_PATCH_SET_NUM} from '../../../utils/patch-set-util.js'; +import {ChangeComments} from '../gr-comment-api/gr-comment-api.js'; const commentApiMockElement = createCommentApiMockWithTemplateElement( 'gr-patch-range-select-comment-api-mock', html` @@ -355,7 +356,7 @@ suite('gr-patch-range-select tests', () => { test('_computePatchSetCommentsString', () => { // Test string with unresolved comments. - element.changeComments._comments = { + const comments = { foo: [{ id: '27dcee4d_f7b77cfa', message: 'test', @@ -377,6 +378,7 @@ suite('gr-patch-range-select tests', () => { }], abc: [], }; + element.changeComments = new ChangeComments(comments, {}, {}, 123); assert.equal(element._computePatchSetCommentsString( element.changeComments, 1), ' (3 comments, 1 unresolved)'); diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts index 4bfacd4452..1c51f99317 100644 --- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts +++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts @@ -27,10 +27,17 @@ import { CustomKeyboardEvent, KeyboardShortcutMixin, } from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin'; -import {sortComments} from '../../diff/gr-comment-api/gr-comment-api'; +import { + isDraft, + isRobot, + sortComments, + UIComment, + UIDraft, + UIRobot, +} from '../../diff/gr-comment-api/gr-comment-api'; import {GerritNav} from '../../core/gr-navigation/gr-navigation'; import {appContext} from '../../../services/app-context'; -import {CommentSide, SpecialFilePath} from '../../../constants/constants'; +import {CommentSide, Side, SpecialFilePath} from '../../../constants/constants'; import {computeDisplayPath} from '../../../utils/path-list-util'; import {customElement, observe, property} from '@polymer/decorators'; import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api'; @@ -42,7 +49,7 @@ import { RepoName, UrlEncodedCommentId, } from '../../../types/common'; -import {Comment, GrComment, RobotComment} from '../gr-comment/gr-comment'; +import {GrComment} from '../gr-comment/gr-comment'; import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces'; import {GrStorage, StorageLocation} from '../gr-storage/gr-storage'; @@ -102,7 +109,7 @@ export class GrCommentThread extends KeyboardShortcutMixin( changeNum?: NumericChangeId; @property({type: Array}) - comments: Comment[] = []; + comments: UIComment[] = []; @property({type: Object, reflectToAttribute: true}) range?: CommentRange; @@ -111,7 +118,7 @@ export class GrCommentThread extends KeyboardShortcutMixin( keyEventTarget: HTMLElement = document.body; @property({type: String, reflectToAttribute: true}) - commentSide?: string; + commentSide?: Side; @property({type: String}) patchNum?: PatchSetNum; @@ -151,10 +158,10 @@ export class GrCommentThread extends KeyboardShortcutMixin( _showActions?: boolean; @property({type: Object}) - _lastComment?: Comment; + _lastComment?: UIComment; @property({type: Array}) - _orderedComments: Comment[] = []; + _orderedComments: UIComment[] = []; @property({type: Object}) _projectConfig?: ConfigInfo; @@ -197,7 +204,7 @@ export class GrCommentThread extends KeyboardShortcutMixin( addOrEditDraft(lineNum?: number, rangeParam?: CommentRange) { const lastComment = this.comments[this.comments.length - 1] || {}; - if (lastComment.__draft) { + if (isDraft(lastComment)) { const commentEl = this._commentElWithDraftID( lastComment.id || lastComment.__draftID ); @@ -237,7 +244,7 @@ export class GrCommentThread extends KeyboardShortcutMixin( _getDiffUrlForPath(path: string) { if (!this.changeNum) throw new Error('changeNum is missing'); if (!this.projectName) throw new Error('projectName is missing'); - if (this.comments[0].__draft) { + if (isDraft(this.comments[0])) { return GerritNav.getUrlForDiffById( this.changeNum, this.projectName, @@ -251,14 +258,15 @@ export class GrCommentThread extends KeyboardShortcutMixin( } _getDiffUrlForComment( - projectName: RepoName, - changeNum: NumericChangeId, - path: string, - patchNum: PatchSetNum + projectName?: RepoName, + changeNum?: NumericChangeId, + path?: string, + patchNum?: PatchSetNum ) { + if (!projectName || !changeNum || !path) return undefined; if ( (this.comments.length && this.comments[0].side === 'PARENT') || - this.comments[0].__draft + isDraft(this.comments[0]) ) { return GerritNav.getUrlForDiffById( changeNum, @@ -271,9 +279,7 @@ export class GrCommentThread extends KeyboardShortcutMixin( } const id = this.comments[0].id; if (!id) throw new Error('A published comment is missing the id.'); - if (!this.changeNum) throw new Error('changeNum is missing'); - if (!this.projectName) throw new Error('projectName is missing'); - return GerritNav.getUrlForComment(this.changeNum, this.projectName, id); + return GerritNav.getUrlForComment(changeNum, projectName, id); } _isPatchsetLevelComment(path: string) { @@ -315,19 +321,19 @@ export class GrCommentThread extends KeyboardShortcutMixin( if (this._orderedComments.length) { this._lastComment = this._getLastComment(); this.unresolved = this._lastComment.unresolved; - this.hasDraft = this._lastComment.__draft; - this.isRobotComment = !!(this._lastComment as RobotComment).robot_id; + this.hasDraft = isDraft(this._lastComment); + this.isRobotComment = isRobot(this._lastComment); } } - _shouldDisableAction(_showActions?: boolean, _lastComment?: Comment) { - return !_showActions || !_lastComment || !!_lastComment.__draft; + _shouldDisableAction(_showActions?: boolean, _lastComment?: UIComment) { + return !_showActions || !_lastComment || isDraft(_lastComment); } - _hideActions(_showActions?: boolean, _lastComment?: Comment) { + _hideActions(_showActions?: boolean, _lastComment?: UIComment) { return ( this._shouldDisableAction(_showActions, _lastComment) || - !!(_lastComment as RobotComment).robot_id + isRobot(_lastComment) ); } @@ -371,7 +377,7 @@ export class GrCommentThread extends KeyboardShortcutMixin( if (this._orderedComments) { for (let i = 0; i < this._orderedComments.length; i++) { const comment = this._orderedComments[i]; - const isRobotComment = !!(comment as RobotComment).robot_id; + const isRobotComment = !!(comment as UIRobot).robot_id; // False if it's an unresolved comment under UNRESOLVED_EXPAND_COUNT. const resolvedThread = !this.unresolved || @@ -417,8 +423,8 @@ export class GrCommentThread extends KeyboardShortcutMixin( } } - _isDraft(comment: Comment) { - return !!comment.__draft; + _isDraft(comment: UIComment) { + return isDraft(comment); } _processCommentReply(quote?: boolean) { @@ -463,9 +469,9 @@ export class GrCommentThread extends KeyboardShortcutMixin( const els = this.root?.querySelectorAll('gr-comment'); if (!els) return null; for (const el of els) { - if (el.comment?.id === id || el.comment?.__draftID === id) { - return el; - } + const c = el.comment; + if (isRobot(c)) continue; + if (c?.id === id || (isDraft(c) && c?.__draftID === id)) return el; } return null; } @@ -487,7 +493,7 @@ export class GrCommentThread extends KeyboardShortcutMixin( } _newDraft(lineNum?: number, range?: CommentRange) { - const d: Comment = { + const d: UIDraft = { __draft: true, __draftID: Math.random().toString(36), __date: new Date(), @@ -529,14 +535,16 @@ export class GrCommentThread extends KeyboardShortcutMixin( return isOnParent ? CommentSide.PARENT : CommentSide.REVISION; } - _computeRootId(comments: PolymerDeepPropertyChange) { + _computeRootId(comments: PolymerDeepPropertyChange) { // Keep the root ID even if the comment was removed, so that notification // to sync will know which thread to remove. if (!comments.base.length) { return this.rootId; } const rootComment = comments.base[0]; - return rootComment.id || rootComment.__draftID; + if (rootComment.id) return rootComment.id; + if (isDraft(rootComment)) return rootComment.__draftID; + throw new Error('Missing id in root comment.'); } _handleCommentDiscard(e: Event) { @@ -599,12 +607,12 @@ export class GrCommentThread extends KeyboardShortcutMixin( this.updateThreadProperties(); } - _indexOf(comment: Comment | undefined, arr: Comment[]) { + _indexOf(comment: UIComment | undefined, arr: UIComment[]) { if (!comment) return -1; for (let i = 0; i < arr.length; i++) { const c = arr[i]; if ( - (c.__draftID && c.__draftID === comment.__draftID) || + (isDraft(c) && isDraft(comment) && c.__draftID === comment.__draftID) || (c.id && c.id === comment.id) ) { return i; diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts index 8bd1f69eeb..d68da9462d 100644 --- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts +++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts @@ -38,22 +38,27 @@ import {htmlTemplate} from './gr-comment_html'; import {KeyboardShortcutMixin} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin'; import {getRootElement} from '../../../scripts/rootElement'; import {appContext} from '../../../services/app-context'; -import {customElement, property, observe} from '@polymer/decorators'; +import {customElement, observe, property} from '@polymer/decorators'; import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api'; import {GrTextarea} from '../gr-textarea/gr-textarea'; import {GrStorage, StorageLocation} from '../gr-storage/gr-storage'; import {GrOverlay} from '../gr-overlay/gr-overlay'; import { - RobotCommentInfo, - PatchSetNum, - CommentInfo, - ConfigInfo, AccountDetailInfo, NumericChangeId, + ConfigInfo, + PatchSetNum, } from '../../../types/common'; import {GrButton} from '../gr-button/gr-button'; import {GrConfirmDeleteCommentDialog} from '../gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog'; import {GrDialog} from '../gr-dialog/gr-dialog'; +import {Side} from '../../../constants/constants'; +import { + isDraft, + UIComment, + UIDraft, + UIRobot, +} from '../../diff/gr-comment-api/gr-comment-api'; const STORAGE_DEBOUNCE_INTERVAL = 400; const TOAST_DEBOUNCE_INTERVAL = 200; @@ -84,23 +89,6 @@ const RESPECTFUL_REVIEW_TIPS = [ 'When disagreeing, explain the advantage of your approach.', ]; -export interface Draft { - collapsed?: boolean; - __editing?: boolean; - __otherEditing?: boolean; - __draft?: boolean; - __draftID?: string; - __commentSide?: string; - __date?: Date; -} - -export type Comment = Draft & CommentInfo; -export type RobotComment = Draft & RobotCommentInfo; - -export function isRobotComment(c: Comment | RobotComment): c is RobotComment { - return (c as RobotComment).robot_id !== undefined; -} - interface CommentOverlays { confirmDelete?: GrOverlay | null; confirmDiscard?: GrOverlay | null; @@ -117,7 +105,7 @@ export interface GrComment { export interface CommentEventDetail { patchNum?: PatchSetNum; - comment?: Comment | RobotComment; + comment?: UIComment; } @customElement('gr-comment') @@ -174,10 +162,10 @@ export class GrComment extends KeyboardShortcutMixin( changeNum?: NumericChangeId; @property({type: Object, notify: true, observer: '_commentChanged'}) - comment?: Comment | RobotComment; + comment?: UIComment | UIRobot; @property({type: Array}) - comments?: (Comment | RobotComment)[]; + comments?: (UIComment | UIRobot)[]; @property({type: Boolean, reflectToAttribute: true}) isRobotComment = false; @@ -235,7 +223,7 @@ export class GrComment extends KeyboardShortcutMixin( _messageText = ''; @property({type: String}) - commentSide?: string; + commentSide?: Side; @property({type: String}) side?: string; @@ -311,7 +299,7 @@ export class GrComment extends KeyboardShortcutMixin( } } - _getAuthor(comment: Comment) { + _getAuthor(comment: UIComment) { return comment.author || this._selfAccount; } @@ -410,7 +398,7 @@ export class GrComment extends KeyboardShortcutMixin( } @observe('comment') - _isRobotComment(comment: RobotComment) { + _isRobotComment(comment: UIRobot) { this.isRobotComment = !!comment.robot_id; } @@ -433,7 +421,7 @@ export class GrComment extends KeyboardShortcutMixin( return 'DRAFT' + (unableToSave ? '(Failed to save)' : ''); } - save(opt_comment?: Comment) { + save(opt_comment?: UIComment) { let comment = opt_comment; if (!comment) { comment = this.comment; @@ -456,7 +444,8 @@ export class GrComment extends KeyboardShortcutMixin( this._eraseDraftComment(); return this.$.restAPI.getResponseObject(response).then(obj => { - const resComment = (obj as unknown) as Comment; + const resComment = (obj as unknown) as UIDraft; + if (!isDraft(this.comment)) throw new Error('Can only save drafts.'); resComment.__draft = true; // Maintain the ephemeral draft ID for identification by other // elements. @@ -495,7 +484,7 @@ export class GrComment extends KeyboardShortcutMixin( }); } - _commentChanged(comment: Comment) { + _commentChanged(comment: UIComment) { this.editing = !!comment.__editing; this.resolved = !comment.unresolved; if (this.editing) { @@ -513,7 +502,7 @@ export class GrComment extends KeyboardShortcutMixin( c => c.in_reply_to && c.in_reply_to === comment.id && - !(c as RobotComment).robot_id + !(c as UIRobot).robot_id ); } @@ -587,7 +576,7 @@ export class GrComment extends KeyboardShortcutMixin( _computeSaveDisabled( draft: string, - comment: Comment | undefined, + comment: UIComment | undefined, resolved?: boolean ) { // If resolved state has changed and a msg exists, save should be enabled. @@ -753,8 +742,8 @@ export class GrComment extends KeyboardShortcutMixin( ); } - _hasNoFix(comment: Comment) { - return !comment || !(comment as RobotComment).fix_suggestions; + _hasNoFix(comment: UIComment) { + return !comment || !(comment as UIRobot).fix_suggestions; } _handleDiscard(e: Event) { @@ -785,7 +774,7 @@ export class GrComment extends KeyboardShortcutMixin( _discardDraft() { if (!this.comment) return Promise.reject(new Error('undefined comment')); - if (!this.comment.__draft) { + if (!isDraft(this.comment)) { return Promise.reject(new Error('Cannot discard a non-draft comment.')); } this.discarding = true; @@ -883,7 +872,7 @@ export class GrComment extends KeyboardShortcutMixin( this._handleFailedDraftRequest(); } - _saveDraft(draft?: Comment) { + _saveDraft(draft?: UIComment) { if (!draft || this.changeNum === undefined || this.patchNum === undefined) { throw new Error('undefined draft or changeNum or patchNum'); } @@ -907,7 +896,7 @@ export class GrComment extends KeyboardShortcutMixin( }); } - _deleteDraft(draft: Comment) { + _deleteDraft(draft: UIComment) { if (this.changeNum === undefined || this.patchNum === undefined) { throw new Error('undefined changeNum or patchNum'); } @@ -937,7 +926,7 @@ export class GrComment extends KeyboardShortcutMixin( _loadLocalDraft( changeNum: number, patchNum?: PatchSetNum, - comment?: Comment + comment?: UIComment ) { // Polymer 2: check for undefined if ([changeNum, patchNum, comment].includes(undefined)) { @@ -1012,7 +1001,7 @@ export class GrComment extends KeyboardShortcutMixin( return overlay.open(); } - _computeHideRunDetails(comment: RobotComment, collapsed: boolean) { + _computeHideRunDetails(comment: UIRobot, collapsed: boolean) { if (!comment) return true; return !(comment.robot_id && comment.url && !collapsed); } diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts index 331fb425c2..2d91d3d6a2 100644 --- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts +++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts @@ -15,19 +15,18 @@ * limitations under the License. */ import {GrAnnotationActionsContext} from './gr-annotation-actions-context'; -import {GrDiffLine, LineNumber} from '../../diff/gr-diff/gr-diff-line'; -import {CoverageRange} from '../../../types/types'; +import {GrDiffLine} from '../../diff/gr-diff/gr-diff-line'; +import { + CoverageRange, + DiffLayer, + DiffLayerListener, +} from '../../../types/types'; import {Side} from '../../../constants/constants'; import {PluginApi} from '../../plugins/gr-plugin-types'; +import {NumericChangeId} from '../../../types/common'; type AddLayerFunc = (ctx: GrAnnotationActionsContext) => void; -type LayerUpdateListener = ( - start: LineNumber, - end: LineNumber, - side: Side -) => void; - type NotifyFunc = ( path: string, start: number, @@ -36,10 +35,10 @@ type NotifyFunc = ( ) => void; export type CoverageProvider = ( - changeNum: number, + changeNum: NumericChangeId, path: string, - basePatchNum: number, - patchNum: number + basePatchNum?: number, + patchNum?: number ) => Promise>; export class GrAnnotationActionsInterface { @@ -205,8 +204,8 @@ export class GrAnnotationActionsInterface { } } -export class AnnotationLayer { - private listeners: LayerUpdateListener[] = []; +export class AnnotationLayer implements DiffLayer { + private listeners: DiffLayerListener[] = []; /** * Used to create an instance of the Annotation Layer interface. @@ -232,12 +231,12 @@ export class AnnotationLayer { * Should accept as arguments the line numbers for the start and end of * the update and the side as a string. */ - addListener(fn: () => void) { - this.listeners.push(fn); + addListener(listener: DiffLayerListener) { + this.listeners.push(listener); } - removeListener(fn: () => void) { - this.listeners = this.listeners.filter(f => f !== fn); + removeListener(listener: DiffLayerListener) { + this.listeners = this.listeners.filter(f => f !== listener); } /** @@ -271,7 +270,7 @@ export class AnnotationLayer { * @param end The line where the update ends. * @param side The side of the update. ('left' or 'right') */ - notifyListeners(start: LineNumber, end: LineNumber, side: Side) { + notifyListeners(start: number, end: number, side: Side) { for (const listener of this.listeners) { listener(start, end, side); } diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.ts index ff9446c261..4fc6f9f895 100644 --- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.ts +++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.ts @@ -21,10 +21,7 @@ import {getPluginLoader} from './gr-plugin-loader'; import {patchNumEquals} from '../../../utils/patch-set-util'; import {customElement} from '@polymer/decorators'; import {ChangeInfo, RevisionInfo} from '../../../types/common'; -import { - CoverageProvider, - GrAnnotationActionsInterface, -} from './gr-annotation-actions-js-api'; +import {GrAnnotationActionsInterface} from './gr-annotation-actions-js-api'; import {GrAdminApi} from '../../plugins/gr-admin-api/gr-admin-api'; import { JsApiService, @@ -33,7 +30,7 @@ import { ShowRevisionActionsDetail, } from './gr-js-api-types'; import {EventType, TargetElement} from '../../plugins/gr-plugin-types'; -import {HighlightJS} from '../../../types/types'; +import {DiffLayer, HighlightJS} from '../../../types/types'; const elements: {[key: string]: HTMLElement} = {}; const eventCallbacks: {[key: string]: EventCallback[]} = {}; @@ -248,7 +245,7 @@ export class GrJsApiInterface } getDiffLayers(path: string, changeNum: number) { - const layers = []; + const layers: DiffLayer[] = []; for (const cb of this._getEventCallbacks(EventType.ANNOTATE_DIFF)) { const annotationApi = (cb as unknown) as GrAnnotationActionsInterface; try { @@ -279,7 +276,9 @@ export class GrJsApiInterface * provider, the first one is returned. If no plugin offers a coverage provider, * will resolve to null. */ - getCoverageAnnotationApi(): Promise { + getCoverageAnnotationApi(): Promise< + GrAnnotationActionsInterface | undefined + > { return getPluginLoader() .awaitPluginsLoaded() .then( @@ -287,7 +286,7 @@ export class GrJsApiInterface this._getEventCallbacks(EventType.ANNOTATE_DIFF).find(cb => { const annotationApi = (cb as unknown) as GrAnnotationActionsInterface; return annotationApi.getCoverageProvider(); - }) as CoverageProvider | undefined + }) as GrAnnotationActionsInterface | undefined ); } diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-types.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-types.ts index b09fe8847e..505e62e36b 100644 --- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-types.ts +++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-types.ts @@ -16,6 +16,8 @@ */ import {ActionInfo, ChangeInfo, PatchSetNum} from '../../../types/common'; import {EventType, TargetElement} from '../../plugins/gr-plugin-types'; +import {DiffLayer} from '../../../types/types'; +import {GrAnnotationActionsInterface} from './gr-annotation-actions-js-api'; export interface ShowChangeDetail { change: ChangeInfo; @@ -45,5 +47,8 @@ export interface JsApiService { origMsg: string ): string; addElement(key: TargetElement, el: HTMLElement): void; + getDiffLayers(path: string, changeNum: number): DiffLayer[]; + disposeDiffLayers(path: string): void; + getCoverageAnnotationApi(): Promise; // TODO(TS): Add more methods when needed for the TS conversion. } diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts index 06ae026dfb..5b9f36069d 100644 --- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts +++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts @@ -133,6 +133,7 @@ import { FixId, FilePathToDiffInfoMap, ChangeViewChangeInfo, + BlameInfo, } from '../../../types/common'; import { CancelConditionCallback, @@ -2524,7 +2525,7 @@ export class GrRestApiInterface req.fetchOptions.headers.append('Cache-Control', 'no-cache'); } - return this._getChangeURLAndFetch(req); + return this._getChangeURLAndFetch(req) as Promise; } getDiffComments( @@ -2639,11 +2640,9 @@ export class GrRestApiInterface _setRanges(comments?: CommentInfo[]) { comments = comments || []; - comments.sort((a, b) => { - if (!a.updated) return 1; - if (!b.updated) return -1; - return parseDate(a.updated).valueOf() - parseDate(b.updated).valueOf(); - }); + comments.sort( + (a, b) => parseDate(a.updated).valueOf() - parseDate(b.updated).valueOf() + ); for (const comment of comments) { this._setRange(comments, comment); } @@ -3472,7 +3471,7 @@ export class GrRestApiInterface patchNum, params: base ? {base: 't'} : undefined, anonymizedEndpoint: '/files/*/blame', - }); + }) as Promise; } /** diff --git a/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts b/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts index 5daf4a9bb9..10715cf1f2 100644 --- a/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts +++ b/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts @@ -87,9 +87,13 @@ import { EmailAddress, FixId, FilePathToDiffInfoMap, + DiffInfo, + BlameInfo, + PatchRange, + ImagesForDiff, } from '../../../types/common'; import {ParsedChangeInfo} from '../../../elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser'; -import {HttpMethod} from '../../../constants/constants'; +import {HttpMethod, IgnoreWhitespaceType} from '../../../constants/constants'; export type ErrorCallback = (response?: Response | null, err?: Error) => void; export type CancelConditionCallback = () => boolean; @@ -735,4 +739,26 @@ export interface RestApiService { patchNum: PatchSetNum, fixId: string ): Promise; + + getDiff( + changeNum: NumericChangeId, + basePatchNum: PatchSetNum, + patchNum: PatchSetNum, + path: string, + whitespace?: IgnoreWhitespaceType, + errFn?: ErrorCallback + ): Promise; + + getBlame( + changeNum: NumericChangeId, + patchNum: PatchSetNum, + path: string, + base?: boolean + ): Promise; + + getImagesForDiff( + changeNum: NumericChangeId, + diff: DiffInfo, + patchRange: PatchRange + ): Promise; } diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts index ca19d702f2..8b02ce9f03 100644 --- a/polygerrit-ui/app/types/common.ts +++ b/polygerrit-ui/app/types/common.ts @@ -1078,7 +1078,7 @@ export interface UserConfigInfo { */ export interface CommentInfo extends CommentInput { patch_set?: PatchSetNum; - id?: UrlEncodedCommentId; + id: UrlEncodedCommentId; path?: string; side?: CommentSide; parent?: number; @@ -1086,7 +1086,7 @@ export interface CommentInfo extends CommentInput { range?: CommentRange; in_reply_to?: UrlEncodedCommentId; message?: string; - updated?: Timestamp; + updated: Timestamp; author?: AccountInfo; tag?: string; unresolved?: boolean; @@ -1876,6 +1876,18 @@ export type RecipientTypeToNotifyInfoMap = { */ export type RobotCommentInput = RobotCommentInfo; +/** + * This is what human, robot and draft comments can agree upon. + * + * Human, robot and saved draft comments all have a required id, but unsaved + * drafts do not. That is why the id is omitted from CommentInfo, such that it + * can be optional in Draft, but required in CommentInfo and RobotCommentInfo. + */ +export interface CommentBasics extends Omit { + id?: UrlEncodedCommentId; + updated?: Timestamp; +} + /** * The RobotCommentInfo entity contains information about a robot inline comment * https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#robot-comment-info diff --git a/polygerrit-ui/app/types/types.ts b/polygerrit-ui/app/types/types.ts index 970fa219ee..3768391228 100644 --- a/polygerrit-ui/app/types/types.ts +++ b/polygerrit-ui/app/types/types.ts @@ -19,6 +19,7 @@ import {IronA11yAnnouncer} from '@polymer/iron-a11y-announcer/iron-a11y-announce import {GrDiffLine} from '../elements/diff/gr-diff/gr-diff-line'; import {FlattenedNodesObserver} from '@polymer/polymer/lib/utils/flattened-nodes-observer'; import {PaperInputElement} from '@polymer/paper-input/paper-input'; +import {CommitId} from './common'; export function notUndefined(x: T | undefined): x is T { return x !== undefined; @@ -28,6 +29,11 @@ export interface FixIronA11yAnnouncer extends IronA11yAnnouncer { requestAvailability(): void; } +export interface CommitRange { + baseCommit: CommitId; + commit: CommitId; +} + export interface CoverageRange { type: CoverageType; side: Side; diff --git a/polygerrit-ui/app/utils/patch-set-util.ts b/polygerrit-ui/app/utils/patch-set-util.ts index 4d688bdb01..962278dc9b 100644 --- a/polygerrit-ui/app/utils/patch-set-util.ts +++ b/polygerrit-ui/app/utils/patch-set-util.ts @@ -3,6 +3,7 @@ import { ChangeInfo, PatchSetNum, EditPatchSetNum, + BrandType, } from '../types/common'; import {RestApiService} from '../services/services/gr-rest-api/gr-rest-api'; import {ParsedChangeInfo} from '../elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser'; @@ -81,6 +82,12 @@ export function isMergeParent(n: PatchSetNum) { return `${n}`[0] === '-'; } +export function isNumber( + psn: PatchSetNum +): psn is BrandType { + return typeof psn === 'number'; +} + /** * Given an object of revisions, get a particular revision based on patch * num.