Merge "Track user interactions with manual and automatic blink buttons"
diff --git a/plugins/replication b/plugins/replication
index 75c44d0..0022a34 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 75c44d0b1dec203859112ad42074eb16839ea353
+Subproject commit 0022a34428cf8bfe4feb0935cdd20b0257bfc8a3
diff --git a/polygerrit-ui/app/api/diff.ts b/polygerrit-ui/app/api/diff.ts
index c2d3044..360ff5c 100644
--- a/polygerrit-ui/app/api/diff.ts
+++ b/polygerrit-ui/app/api/diff.ts
@@ -53,6 +53,35 @@
}
/**
+ * Represents a syntax block in a code (e.g. method, function, class, if-else).
+ */
+export interface SyntaxBlock {
+ /** Name of the block (e.g. name of the method/class)*/
+ name: string;
+ /** Where does this block syntatically starts and ends (line number and column).*/
+ range: {
+ /** first line of the block (1-based inclusive). */
+ start_line: number;
+ /**
+ * column of the range start inside the first line (e.g. "{" character ending a function/method)
+ * (1-based inclusive).
+ */
+ start_column: number;
+ /**
+ * last line of the block (1-based inclusive).
+ */
+ end_line: number;
+ /**
+ * column of the block end inside the end line (e.g. "}" character ending a function/method)
+ * (1-based inclusive).
+ */
+ end_column: number;
+ };
+ /** Sub-blocks of the current syntax block (e.g. methods of a class) */
+ children: SyntaxBlock[];
+}
+
+/**
* The DiffFileMetaInfo entity contains meta information about a file diff.
* https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#diff-file-meta-info
*/
@@ -65,6 +94,12 @@
lines: number;
// TODO: Not documented.
language?: string;
+ /**
+ * The first level of syntax blocks tree (outline) within the current file.
+ * It contains an hierarchical structure where each block contains its
+ * sub-blocks (children).
+ */
+ syntax_tree?: SyntaxBlock[];
}
export declare type ChangeType =
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index 827cbf1..b4d25a6 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -122,6 +122,7 @@
DraftInfo,
isDraftThread,
isRobot,
+ isUnresolved,
} from '../../../utils/comment-util';
import {
PolymerDeepPropertyChange,
@@ -901,6 +902,13 @@
return false;
}
+ _computeShowUnresolved(threads?: CommentThread[]) {
+ // If all threads are resolved and the Comments Tab is opened then show
+ // all threads instead
+ if (!threads?.length) return true;
+ return threads.filter(thread => isUnresolved(thread)).length > 0;
+ }
+
_robotCommentCountPerPatchSet(threads: CommentThread[]) {
return threads.reduce((robotCommentCountMap, thread) => {
const comments = thread.comments;
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
index ae4ad79..6025268 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
@@ -570,7 +570,7 @@
logged-in="[[_loggedIn]]"
comment-tab-state="[[_tabState.commentTab]]"
only-show-robot-comments-with-human-reply=""
- unresolved-only
+ unresolved-only="[[_computeShowUnresolved(_commentThreads)]]"
show-comment-context
></gr-thread-list>
</template>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
index bb3c975..ca16ec0 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
@@ -618,52 +618,20 @@
return changeComments.computeCommentsString(patchRange, file.__path, file);
}
- _computeDraftCount(
- changeComments?: ChangeComments,
- patchRange?: PatchRange,
- path?: string
- ) {
- if (
- changeComments === undefined ||
- patchRange === undefined ||
- path === undefined
- ) {
- return '';
- }
- return (
- changeComments.computeDraftCount({
- patchNum: patchRange.basePatchNum,
- path,
- }) +
- changeComments.computeDraftCount({
- patchNum: patchRange.patchNum,
- path,
- }) +
- changeComments.computePortedDraftCount(
- {
- patchNum: patchRange.patchNum,
- basePatchNum: patchRange.basePatchNum,
- },
- path
- )
- );
- }
-
/**
* Computes a string with the number of drafts.
*/
_computeDraftsString(
changeComments?: ChangeComments,
patchRange?: PatchRange,
- path?: string
+ file?: NormalizedFileInfo
) {
- const draftCount = this._computeDraftCount(
- changeComments,
+ const draftCount = changeComments?.computeDraftCountForFile(
patchRange,
- path
+ file
);
- if (draftCount === '') return draftCount;
- return pluralize(draftCount, 'draft');
+ if (draftCount === 0) return '';
+ return pluralize(Number(draftCount), 'draft');
}
/**
@@ -672,12 +640,11 @@
_computeDraftsStringMobile(
changeComments?: ChangeComments,
patchRange?: PatchRange,
- path?: string
+ file?: NormalizedFileInfo
) {
- const draftCount = this._computeDraftCount(
- changeComments,
+ const draftCount = changeComments?.computeDraftCountForFile(
patchRange,
- path
+ file
);
return draftCount === 0 ? '' : `${draftCount}d`;
}
@@ -688,23 +655,23 @@
_computeCommentsStringMobile(
changeComments?: ChangeComments,
patchRange?: PatchRange,
- path?: string
+ file?: NormalizedFileInfo
) {
if (
changeComments === undefined ||
patchRange === undefined ||
- path === undefined
+ file === undefined
) {
return '';
}
const commentThreadCount =
changeComments.computeCommentThreadCount({
patchNum: patchRange.basePatchNum,
- path,
+ path: file.__path,
}) +
changeComments.computeCommentThreadCount({
patchNum: patchRange.patchNum,
- path,
+ path: file.__path,
});
return commentThreadCount === 0 ? '' : `${commentThreadCount}c`;
}
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts
index 59338df..40bd5bc 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts
@@ -423,8 +423,7 @@
<span class="drafts"
><!-- This comments ensure that span is empty when the function
returns empty string.
- -->[[_computeDraftsString(changeComments, patchRange,
- file.__path)]]<!-- This comments ensure that span is empty when
+ -->[[_computeDraftsString(changeComments, patchRange, file)]]<!-- This comments ensure that span is empty when
the function returns empty string.
--></span
>
@@ -450,14 +449,14 @@
><!-- This comments ensure that span is empty when the function
returns empty string.
-->[[_computeDraftsStringMobile(changeComments, patchRange,
- file.__path)]]<!-- This comments ensure that span is empty when
+ file)]]<!-- This comments ensure that span is empty when
the function returns empty string.
--></span
>
<span
><!--
-->[[_computeCommentsStringMobile(changeComments, patchRange,
- file.__path)]]<!--
+ file)]]<!--
--></span
>
<span class="noCommentsScreenReaderText">
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 b8ba86c..dcc2e46 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
@@ -360,103 +360,103 @@
assert.equal(
element._computeCommentsStringMobile(element.changeComments, parentTo1
- , '/COMMIT_MSG'), '2c');
+ , {__path: '/COMMIT_MSG'}), '2c');
assert.equal(
element._computeCommentsStringMobile(element.changeComments, _1To2
- , '/COMMIT_MSG'), '3c');
+ , {__path: '/COMMIT_MSG'}), '3c');
assert.equal(
element._computeDraftsString(element.changeComments, parentTo1,
- 'unresolved.file'), '1 draft');
+ {__path: 'unresolved.file'}), '1 draft');
assert.equal(
element._computeDraftsString(element.changeComments, _1To2,
- 'unresolved.file'), '1 draft');
+ {__path: 'unresolved.file'}), '1 draft');
assert.equal(
element._computeDraftsStringMobile(element.changeComments, parentTo1,
- 'unresolved.file'), '1d');
+ {__path: 'unresolved.file'}), '1d');
assert.equal(
element._computeDraftsStringMobile(element.changeComments, _1To2,
- 'unresolved.file'), '1d');
+ {__path: 'unresolved.file'}), '1d');
assert.equal(
element._computeCommentsStringMobile(
element.changeComments,
parentTo1,
- 'myfile.txt'
+ {__path: 'myfile.txt'}
), '1c');
assert.equal(
element._computeCommentsStringMobile(element.changeComments, _1To2,
- 'myfile.txt'), '3c');
+ {__path: 'myfile.txt'}), '3c');
assert.equal(
element._computeDraftsString(element.changeComments, parentTo1,
- 'myfile.txt'), '');
+ {__path: 'myfile.txt'}), '');
assert.equal(
element._computeDraftsString(element.changeComments, _1To2,
- 'myfile.txt'), '');
+ {__path: 'myfile.txt'}), '');
assert.equal(
element._computeDraftsStringMobile(element.changeComments, parentTo1,
- 'myfile.txt'), '');
+ {__path: 'myfile.txt'}), '');
assert.equal(
element._computeDraftsStringMobile(element.changeComments, _1To2,
- 'myfile.txt'), '');
+ {__path: 'myfile.txt'}), '');
assert.equal(
element._computeCommentsStringMobile(
element.changeComments,
parentTo1,
- 'file_added_in_rev2.txt'
+ {__path: 'file_added_in_rev2.txt'}
), '');
assert.equal(
element._computeCommentsStringMobile(element.changeComments, _1To2,
- 'file_added_in_rev2.txt'), '');
+ {__path: 'file_added_in_rev2.txt'}), '');
assert.equal(
element._computeDraftsString(element.changeComments, parentTo1,
- 'file_added_in_rev2.txt'), '');
+ {__path: 'file_added_in_rev2.txt'}), '');
assert.equal(
element._computeDraftsString(element.changeComments, _1To2,
- 'file_added_in_rev2.txt'), '');
+ {__path: 'file_added_in_rev2.txt'}), '');
assert.equal(
element._computeDraftsStringMobile(element.changeComments, parentTo1,
- 'file_added_in_rev2.txt'), '');
+ {__path: 'file_added_in_rev2.txt'}), '');
assert.equal(
element._computeDraftsStringMobile(element.changeComments, _1To2,
- 'file_added_in_rev2.txt'), '');
+ {__path: 'file_added_in_rev2.txt'}), '');
assert.equal(
element._computeCommentsStringMobile(
element.changeComments,
parentTo2,
- '/COMMIT_MSG'
+ {__path: '/COMMIT_MSG'}
), '1c');
assert.equal(
element._computeCommentsStringMobile(element.changeComments, _1To2,
- '/COMMIT_MSG'), '3c');
+ {__path: '/COMMIT_MSG'}), '3c');
assert.equal(
element._computeDraftsString(element.changeComments, parentTo1,
- '/COMMIT_MSG'), '2 drafts');
+ {__path: '/COMMIT_MSG'}), '2 drafts');
assert.equal(
element._computeDraftsString(element.changeComments, _1To2,
- '/COMMIT_MSG'), '2 drafts');
+ {__path: '/COMMIT_MSG'}), '2 drafts');
assert.equal(
element._computeDraftsStringMobile(
element.changeComments,
parentTo1,
- '/COMMIT_MSG'
+ {__path: '/COMMIT_MSG'}
), '2d');
assert.equal(
element._computeDraftsStringMobile(element.changeComments, _1To2,
- '/COMMIT_MSG'), '2d');
+ {__path: '/COMMIT_MSG'}), '2d');
assert.equal(
element._computeCommentsStringMobile(
element.changeComments,
parentTo2,
- 'myfile.txt'
+ {__path: 'myfile.txt'}
), '2c');
assert.equal(
element._computeCommentsStringMobile(element.changeComments, _1To2,
- 'myfile.txt'), '3c');
+ {__path: 'myfile.txt'}), '3c');
assert.equal(
element._computeDraftsStringMobile(element.changeComments, parentTo2,
- 'myfile.txt'), '');
+ {__path: 'myfile.txt'}), '');
assert.equal(
element._computeDraftsStringMobile(element.changeComments, _1To2,
- 'myfile.txt'), '');
+ {__path: 'myfile.txt'}), '');
});
test('_reviewedTitle', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
index fed02a7..26af2a2 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
@@ -62,6 +62,7 @@
const LABEL_TITLE_SCORE_PATTERN = /^(-?)([A-Za-z0-9-]+?)([+-]\d+)?[.]?$/;
const UPLOADED_NEW_PATCHSET_PATTERN = /Uploaded patch set (\d+)./;
const MERGED_PATCHSET_PATTERN = /(\d+) is the latest approved patch-set/;
+const VOTE_RESET_TEXT = '0 (vote reset)';
declare global {
interface HTMLElementTagNameMap {
@@ -466,7 +467,7 @@
)
.map(ms => {
const label = ms?.[2];
- const value = ms?.[1] === '-' ? 'removed' : ms?.[3];
+ const value = ms?.[1] === '-' ? VOTE_RESET_TEXT : ms?.[3];
return {label, value};
});
}
@@ -479,7 +480,7 @@
if (!score.value) {
return '';
}
- if (score.value === 'removed') {
+ if (score.value.includes(VOTE_RESET_TEXT)) {
return 'removed';
}
const classes = [];
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 8cc7a3f..73afde8 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
@@ -51,6 +51,7 @@
import {appContext} from '../../../services/app-context';
import {CommentSide, Side} from '../../../constants/constants';
import {pluralize} from '../../../utils/string-util';
+import {NormalizedFileInfo} from '../../change/gr-file-list/gr-file-list';
export type CommentIdToCommentThreadMap = {
[urlEncodedCommentId: string]: CommentThread;
@@ -522,6 +523,33 @@
.length;
}
+ computeDraftCountForFile(patchRange?: PatchRange, file?: NormalizedFileInfo) {
+ if (patchRange === undefined || file === undefined) {
+ return 0;
+ }
+ const getCommentForPath = (path?: string) => {
+ if (!path) return 0;
+ return (
+ this.computeDraftCount({
+ patchNum: patchRange.basePatchNum,
+ path,
+ }) +
+ this.computeDraftCount({
+ patchNum: patchRange.patchNum,
+ path,
+ }) +
+ this.computePortedDraftCount(
+ {
+ patchNum: patchRange.patchNum,
+ basePatchNum: patchRange.basePatchNum,
+ },
+ path
+ )
+ );
+ };
+ return getCommentForPath(file.__path) + getCommentForPath(file.old_path);
+ }
+
/**
* @param includeUnmodified Included unmodified status of the file in the
* comment string or not. For files we opt of chip instead of a string.
@@ -537,6 +565,14 @@
if (!patchRange) return '';
const threads = this.getThreadsBySideForFile({path}, patchRange);
+ if (changeFileInfo?.old_path) {
+ threads.push(
+ ...this.getThreadsBySideForFile(
+ {path: changeFileInfo.old_path},
+ patchRange
+ )
+ );
+ }
const commentThreadCount = threads.filter(thread => !isDraftThread(thread))
.length;
const unresolvedCount = threads.reduce((cnt, thread) => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
index 7c01a95..5ba3606 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
@@ -97,8 +97,13 @@
return section;
}
+ let diffInfo;
+ let renderPrefs;
+
setup(() => {
- builder = new GrDiffBuilder({content: []}, prefs, null, []);
+ diffInfo = {content: []};
+ renderPrefs = {};
+ builder = new GrDiffBuilder(diffInfo, prefs, null, [], renderPrefs);
});
test('no +10 buttons for 10 or less lines', () => {
@@ -149,6 +154,70 @@
assert.include([...buttons[0].classList.values()], 'aboveButton');
assert.include([...buttons[1].classList.values()], 'aboveButton');
});
+
+ suite('with block expansion', () => {
+ setup(() => {
+ builder._numLinesLeft = 50;
+ renderPrefs.use_block_expansion = true;
+ diffInfo.meta_b = {
+ syntax_tree: [],
+ };
+ });
+
+ test('context control with block expansion at the top', () => {
+ const section = createContextSectionForGroups({offset: 0, count: 20});
+
+ const fullExpansionButtons = section
+ .querySelectorAll('.fullExpansion gr-button');
+ const partialExpansionButtons = section
+ .querySelectorAll('.partialExpansion gr-button');
+ const blockExpansionButtons = section
+ .querySelectorAll('.blockExpansion gr-button');
+ assert.equal(fullExpansionButtons.length, 1);
+ assert.equal(partialExpansionButtons.length, 1);
+ assert.equal(blockExpansionButtons.length, 1);
+ assert.equal(blockExpansionButtons[0].textContent, '+Block');
+ assert.include([...blockExpansionButtons[0].classList.values()],
+ 'belowButton');
+ });
+
+ test('context control in the middle', () => {
+ const section = createContextSectionForGroups({offset: 10, count: 20});
+
+ const fullExpansionButtons = section
+ .querySelectorAll('.fullExpansion gr-button');
+ const partialExpansionButtons = section
+ .querySelectorAll('.partialExpansion gr-button');
+ const blockExpansionButtons = section
+ .querySelectorAll('.blockExpansion gr-button');
+ assert.equal(fullExpansionButtons.length, 1);
+ assert.equal(partialExpansionButtons.length, 2);
+ assert.equal(blockExpansionButtons.length, 2);
+ assert.equal(blockExpansionButtons[0].textContent, '+Block');
+ assert.equal(blockExpansionButtons[1].textContent, '+Block');
+ assert.include([...blockExpansionButtons[0].classList.values()],
+ 'aboveButton');
+ assert.include([...blockExpansionButtons[1].classList.values()],
+ 'belowButton');
+ });
+
+ test('context control at the bottom', () => {
+ const section = createContextSectionForGroups({offset: 30, count: 20});
+
+ const fullExpansionButtons = section
+ .querySelectorAll('.fullExpansion gr-button');
+ const partialExpansionButtons = section
+ .querySelectorAll('.partialExpansion gr-button');
+ const blockExpansionButtons = section
+ .querySelectorAll('.blockExpansion gr-button');
+ assert.equal(fullExpansionButtons.length, 1);
+ assert.equal(partialExpansionButtons.length, 1);
+ assert.equal(blockExpansionButtons.length, 1);
+ assert.equal(blockExpansionButtons[0].textContent, '+Block');
+ assert.include([...blockExpansionButtons[0].classList.values()],
+ 'aboveButton');
+ });
+ });
});
test('newlines 1', () => {
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 c8b69f0..d5e6ecd 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
@@ -20,6 +20,7 @@
DiffContextExpandedExternalDetail,
MovedLinkClickedEventDetail,
RenderPreferences,
+ SyntaxBlock,
} from '../../../api/diff';
import {getBaseUrl} from '../../../utils/url-util';
import {GrDiffLine, GrDiffLineType, LineNumber} from '../gr-diff/gr-diff-line';
@@ -73,6 +74,19 @@
}
}
+function findMostNestedContainingBlock(
+ lineNum: number,
+ blocks?: SyntaxBlock[]
+): SyntaxBlock | undefined {
+ const containingBlock = blocks?.find(
+ ({range}) => range.start_line < lineNum && range.end_line > lineNum
+ );
+ const containingChildBlock = containingBlock
+ ? findMostNestedContainingBlock(lineNum, containingBlock?.children)
+ : undefined;
+ return containingChildBlock || containingBlock;
+}
+
export abstract class GrDiffBuilder {
private readonly _diff: DiffInfo;
@@ -306,6 +320,7 @@
);
}
+ // TODO(renanoliveira): Move context controls to polymer component (or at least a separate class).
_createContextControls(
section: HTMLElement,
contextGroups: GrDiffGroup[],
@@ -314,9 +329,9 @@
const leftStart = contextGroups[0].lineRange.left.start_line;
const leftEnd =
contextGroups[contextGroups.length - 1].lineRange.left.end_line;
- const numLines = leftEnd - leftStart + 1;
-
- if (numLines === 0) console.error('context group without lines');
+ const rightStart = contextGroups[0].lineRange.right.start_line;
+ const rightEnd =
+ contextGroups[contextGroups.length - 1].lineRange.right.end_line;
const firstGroupIsSkipped = !!contextGroups[0].skip;
const lastGroupIsSkipped = !!contextGroups[contextGroups.length - 1].skip;
@@ -335,7 +350,8 @@
contextGroups,
showAbove,
showBelow,
- numLines
+ rightStart,
+ rightEnd
)
);
if (showBelow) {
@@ -354,8 +370,12 @@
contextGroups: GrDiffGroup[],
showAbove: boolean,
showBelow: boolean,
- numLines: number
+ rightStart: number,
+ rightEnd: number
): HTMLElement {
+ const numLines = rightEnd - rightStart + 1;
+ if (numLines === 0) console.error('context group without lines');
+
const row = this._createElement('tr', 'contextDivider');
if (!(showAbove && showBelow)) {
row.classList.add('collapsed');
@@ -364,13 +384,55 @@
const element = this._createElement('td', 'dividerCell');
row.appendChild(element);
- const showAllContainer = this._createElement('div', 'aboveBelowButtons');
+ const showAllContainer = this._createExpandAllButtonContainer(
+ section,
+ contextGroups,
+ showAbove,
+ showBelow,
+ numLines
+ );
element.appendChild(showAllContainer);
+ const showPartialLinks = numLines > PARTIAL_CONTEXT_AMOUNT;
+ if (showPartialLinks) {
+ const partialExpansionContainer = this._createPartialExpansionButtons(
+ section,
+ contextGroups,
+ showAbove,
+ showBelow,
+ numLines
+ );
+ if (partialExpansionContainer) {
+ element.appendChild(partialExpansionContainer);
+ }
+ const blockExpansionContainer = this._createBlockExpansionButtons(
+ section,
+ contextGroups,
+ showAbove,
+ showBelow,
+ rightStart,
+ rightEnd,
+ numLines
+ );
+ if (blockExpansionContainer) {
+ element.appendChild(blockExpansionContainer);
+ }
+ }
+ return row;
+ }
+
+ private _createExpandAllButtonContainer(
+ section: HTMLElement,
+ contextGroups: GrDiffGroup[],
+ showAbove: boolean,
+ showBelow: boolean,
+ numLines: number
+ ) {
const showAllButton = this._createContextButton(
ContextButtonType.ALL,
section,
contextGroups,
+ numLines,
numLines
);
showAllButton.classList.add(
@@ -380,61 +442,131 @@
? 'aboveButton'
: 'belowButton'
);
+ const showAllContainer = this._createElement(
+ 'div',
+ 'aboveBelowButtons fullExpansion'
+ );
showAllContainer.appendChild(showAllButton);
+ return showAllContainer;
+ }
- const showPartialLinks = numLines > PARTIAL_CONTEXT_AMOUNT;
- if (showPartialLinks) {
- const container = this._createElement('div', 'aboveBelowButtons');
- if (showAbove) {
- container.appendChild(
- this._createContextButton(
- ContextButtonType.ABOVE,
- section,
- contextGroups,
- numLines
- )
- );
- }
- if (showBelow) {
- container.appendChild(
- this._createContextButton(
- ContextButtonType.BELOW,
- section,
- contextGroups,
- numLines
- )
- );
- }
- element.appendChild(container);
- if (this._renderPrefs?.use_block_expansion) {
- const blockExpansionContainer = this._createElement(
- 'div',
- 'aboveBelowButtons'
- );
- if (showAbove) {
- blockExpansionContainer.appendChild(
- this._createContextButton(
- ContextButtonType.BLOCK_ABOVE,
- section,
- contextGroups,
- numLines
- )
- );
- }
- if (showBelow) {
- blockExpansionContainer.appendChild(
- this._createContextButton(
- ContextButtonType.BLOCK_BELOW,
- section,
- contextGroups,
- numLines
- )
- );
- }
- element.appendChild(blockExpansionContainer);
+ private _createPartialExpansionButtons(
+ section: HTMLElement,
+ contextGroups: GrDiffGroup[],
+ showAbove: boolean,
+ showBelow: boolean,
+ numLines: number
+ ) {
+ let aboveButton;
+ let belowButton;
+ if (showAbove) {
+ aboveButton = this._createContextButton(
+ ContextButtonType.ABOVE,
+ section,
+ contextGroups,
+ numLines,
+ PARTIAL_CONTEXT_AMOUNT
+ );
+ }
+ if (showBelow) {
+ belowButton = this._createContextButton(
+ ContextButtonType.BELOW,
+ section,
+ contextGroups,
+ numLines,
+ PARTIAL_CONTEXT_AMOUNT
+ );
+ }
+ if (aboveButton || belowButton) {
+ const partialExpansionContainer = this._createElement(
+ 'div',
+ 'aboveBelowButtons partialExpansion'
+ );
+ aboveButton && partialExpansionContainer.appendChild(aboveButton);
+ belowButton && partialExpansionContainer.appendChild(belowButton);
+ return partialExpansionContainer;
+ }
+ return undefined;
+ }
+
+ private _createBlockExpansionButtons(
+ section: HTMLElement,
+ contextGroups: GrDiffGroup[],
+ showAbove: boolean,
+ showBelow: boolean,
+ rightStart: number,
+ rightEnd: number,
+ numLines: number
+ ) {
+ if (!this._renderPrefs?.use_block_expansion) {
+ return undefined;
+ }
+ let aboveBlockButton;
+ let belowBlockButton;
+ const rightSyntaxTree = this._diff.meta_b.syntax_tree;
+ if (showAbove) {
+ aboveBlockButton = this._createBlockButton(
+ section,
+ contextGroups,
+ ContextButtonType.BLOCK_ABOVE,
+ numLines,
+ rightStart - 1,
+ rightSyntaxTree
+ );
+ }
+ if (showBelow) {
+ belowBlockButton = this._createBlockButton(
+ section,
+ contextGroups,
+ ContextButtonType.BLOCK_BELOW,
+ numLines,
+ rightEnd + 1,
+ rightSyntaxTree
+ );
+ }
+ if (aboveBlockButton || belowBlockButton) {
+ const blockExpansionContainer = this._createElement(
+ 'div',
+ 'blockExpansion aboveBelowButtons'
+ );
+ aboveBlockButton && blockExpansionContainer.appendChild(aboveBlockButton);
+ belowBlockButton && blockExpansionContainer.appendChild(belowBlockButton);
+ return blockExpansionContainer;
+ }
+ return undefined;
+ }
+
+ private _createBlockButton(
+ section: HTMLElement,
+ contextGroups: GrDiffGroup[],
+ buttonType: ContextButtonType,
+ numLines: number,
+ referenceLine: number,
+ syntaxTree?: SyntaxBlock[]
+ ) {
+ const containingBlock = findMostNestedContainingBlock(
+ referenceLine,
+ syntaxTree
+ );
+ let linesToExpand = numLines;
+ if (containingBlock) {
+ const {range} = containingBlock;
+ const targetLine =
+ buttonType === ContextButtonType.BLOCK_ABOVE
+ ? range.end_line
+ : range.start_line;
+ const distanceToTargetLine = Math.abs(targetLine - referenceLine);
+ if (distanceToTargetLine < numLines) {
+ linesToExpand = distanceToTargetLine;
}
}
- return row;
+ return this._createContextButton(
+ buttonType,
+ section,
+ contextGroups,
+ numLines,
+ linesToExpand
+ );
}
/**
@@ -469,10 +601,9 @@
type: ContextButtonType,
section: HTMLElement,
contextGroups: GrDiffGroup[],
- numLines: number
+ numLines: number,
+ linesToExpand: number
) {
- const linesToExpand =
- type === ContextButtonType.ALL ? numLines : PARTIAL_CONTEXT_AMOUNT;
const button = this._createElement('gr-button', 'showContext');
button.classList.add('contextControlButton');
button.setAttribute('link', 'true');
diff --git a/polygerrit-ui/app/utils/path-list-util.ts b/polygerrit-ui/app/utils/path-list-util.ts
index dda6031..fd922fc 100644
--- a/polygerrit-ui/app/utils/path-list-util.ts
+++ b/polygerrit-ui/app/utils/path-list-util.ts
@@ -64,6 +64,8 @@
return file === SpecialFilePath.PATCHSET_LEVEL_COMMENTS;
}
+// In case there are files with comments on them but they are unchanged, then
+// we explicitly displays the file to render the comments with Unchanged status
export function addUnmodifiedFiles(
files: {[filename: string]: FileInfo},
commentedPaths: {[fileName: string]: boolean}
@@ -73,6 +75,18 @@
if (hasOwnProperty(files, commentedPath) || shouldHideFile(commentedPath)) {
return;
}
+
+ // if file is Renamed but has comments, then do not show the entry for the
+ // old file path name
+ if (
+ Object.values(files).some(
+ file =>
+ file.status === FileInfoStatus.RENAMED &&
+ file.old_path === commentedPath
+ )
+ ) {
+ return;
+ }
// TODO(TS): either change FileInfo to mark delta and size optional
// or fill in 0 here
files[commentedPath] = {