Merge "Migrate gr-label to Lit"
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index a8d8769..b609643 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -600,8 +600,9 @@
----
As a response, two link:#change-info[ChangeInfo] entities are returned
-that describe information added and removed from the `old` change state.
-Only fields that differ between the change's two states are returned.
+that describe information added and removed from the `old` change state, and
+the two link:#change-info[ChangeInfo] entities that generated the diff are
+returned. Only fields that differ between the change's two states are returned.
.Response
----
@@ -625,9 +626,55 @@
"topic": "new-topic"
},
"removed": {
- "updated": "2013-02-20 12:05:34.111000000",
+ "updated": "2013-02-01 09:59:32.126000000",
"topic": "old-topic"
- }
+ },
+ "old_change_info": {
+ "id": "myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940",
+ "project": "myProject",
+ "branch": "master",
+ "attention_set": [],
+ "change_id": "I8473b95934b5732ac55d26311a706c9c2bde9940",
+ "subject": "Implementing Feature X",
+ "status": "NEW",
+ "topic": "old-topic",
+ "created": "2013-02-01 09:59:32.126000000",
+ "updated": "2013-02-01 09:59:32.126000000",
+ "mergeable": true,
+ "insertions": 34,
+ "deletions": 101,
+ "_number": 3965,
+ "owner": {
+ "name": "John Doe"
+ }
+ },
+ "new_change_info": {
+ "id": "myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940",
+ "project": "myProject",
+ "branch": "master",
+ "attention_set": [
+ {
+ "account": {
+ "name": "John Doe"
+ },
+ "last_update": "2013-02-21 11:16:36.775000000",
+ "reason": "reviewer or cc replied"
+ }
+ ],
+ "change_id": "I8473b95934b5732ac55d26311a706c9c2bde9940",
+ "subject": "Implementing Feature X",
+ "status": "NEW",
+ "topic": "new-topic",
+ "created": "2013-02-01 09:59:32.126000000",
+ "updated": "2013-02-21 11:16:36.775000000",
+ "mergeable": true,
+ "insertions": 34,
+ "deletions": 101,
+ "_number": 3965,
+ "owner": {
+ "name": "John Doe"
+ }
+ },
}
----
diff --git a/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java b/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
index ba5e323..ad112d3 100644
--- a/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
+++ b/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
@@ -63,9 +63,12 @@
*/
public static ChangeInfoDifference getDifference(
ChangeInfo oldChangeInfo, ChangeInfo newChangeInfo) {
- return ChangeInfoDifference.create(
- /* added= */ getAdded(oldChangeInfo, newChangeInfo),
- /* removed= */ getAdded(newChangeInfo, oldChangeInfo));
+ return ChangeInfoDifference.builder()
+ .setOldChangeInfo(oldChangeInfo)
+ .setNewChangeInfo(newChangeInfo)
+ .setAdded(getAdded(oldChangeInfo, newChangeInfo))
+ .setRemoved(getAdded(newChangeInfo, oldChangeInfo))
+ .build();
}
@SuppressWarnings("unchecked") // reflection is used to construct instances of T
diff --git a/java/com/google/gerrit/extensions/common/ChangeInfoDifference.java b/java/com/google/gerrit/extensions/common/ChangeInfoDifference.java
index 269c673..997c3ee 100644
--- a/java/com/google/gerrit/extensions/common/ChangeInfoDifference.java
+++ b/java/com/google/gerrit/extensions/common/ChangeInfoDifference.java
@@ -20,11 +20,29 @@
@AutoValue
public abstract class ChangeInfoDifference {
+ public abstract ChangeInfo oldChangeInfo();
+
+ public abstract ChangeInfo newChangeInfo();
+
public abstract ChangeInfo added();
public abstract ChangeInfo removed();
- public static ChangeInfoDifference create(ChangeInfo added, ChangeInfo removed) {
- return new AutoValue_ChangeInfoDifference(added, removed);
+ public static Builder builder() {
+ return new AutoValue_ChangeInfoDifference.Builder();
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ public abstract Builder setOldChangeInfo(ChangeInfo oldChangeInfo);
+
+ public abstract Builder setNewChangeInfo(ChangeInfo newChangeInfo);
+
+ public abstract Builder setAdded(ChangeInfo added);
+
+ public abstract Builder setRemoved(ChangeInfo removed);
+
+ public abstract ChangeInfoDifference build();
}
}
diff --git a/javatests/com/google/gerrit/extensions/common/ChangeInfoDifferTest.java b/javatests/com/google/gerrit/extensions/common/ChangeInfoDifferTest.java
index 4352fe8..024e35e 100644
--- a/javatests/com/google/gerrit/extensions/common/ChangeInfoDifferTest.java
+++ b/javatests/com/google/gerrit/extensions/common/ChangeInfoDifferTest.java
@@ -58,6 +58,17 @@
}
@Test
+ public void getDiff_returnsOldAndNewChangeInfos() {
+ ChangeInfo oldChangeInfo = createChangeInfoWithTopic("topic");
+ ChangeInfo newChangeInfo = createChangeInfoWithTopic(oldChangeInfo.topic);
+
+ ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+ assertThat(diff.oldChangeInfo()).isEqualTo(oldChangeInfo);
+ assertThat(diff.newChangeInfo()).isEqualTo(newChangeInfo);
+ }
+
+ @Test
public void getDiff_givenUnchangedTopic_returnsNullTopics() {
ChangeInfo oldChangeInfo = createChangeInfoWithTopic("topic");
ChangeInfo newChangeInfo = createChangeInfoWithTopic(oldChangeInfo.topic);
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index c8949e6..9ebee9c 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -3698,18 +3698,16 @@
TestRepository<Repo> repo = createProject("repo");
Change change = insert(repo, newChange(repo));
- AssigneeInput ain = new AssigneeInput();
- ain.assignee = user2.toString();
- gApi.changes().id(change.getId().get()).setAssignee(ain);
+ gApi.changes().id(change.getId().get()).addReviewer(user2.toString());
RequestContext adminContext = requestContext.setContext(newRequestContext(user2));
- assertQuery("assignee:self", change);
+ assertQuery("reviewer:self", change);
requestContext.setContext(adminContext);
gApi.accounts().id(user2.get()).setActive(false);
requestContext.setContext(newRequestContext(user2));
- assertQuery("assignee:self", change);
+ assertQuery("reviewer:self", change);
}
@Test
diff --git a/polygerrit-ui/app/api/diff.ts b/polygerrit-ui/app/api/diff.ts
index 1453fd0..ee579ff 100644
--- a/polygerrit-ui/app/api/diff.ts
+++ b/polygerrit-ui/app/api/diff.ts
@@ -53,30 +53,30 @@
}
/**
+ * Represents a "generic" text range in the code (e.g. text selection)
+ */
+interface TextRange {
+ /** first line of the range (1-based inclusive). */
+ start_line: number;
+ /** first column of the range (in the first line) (1-based inclusive). */
+ start_column: number;
+ /** last line of the range (1-based inclusive). */
+ end_line: number;
+ /** last column of the range (in the end line) (1-based inclusive). */
+ end_column: number;
+}
+
+/**
* Represents a syntax block in a code (e.g. method, function, class, if-else).
*/
export declare 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;
- };
+ /**
+ * Where does this block syntatically starts and ends (line number and
+ * column).
+ */
+ range: TextRange;
/** Sub-blocks of the current syntax block (e.g. methods of a class) */
children: SyntaxBlock[];
}
@@ -210,15 +210,22 @@
}
/**
- * Listens to changes in token highlighting - when a new token starts or stopped being highlighted.
- * Examples:
- * - Token highlighted: ('myFunctionName', 12, [Element]).
- * - Token unhighlighted: (undefined, 0, undefined).
+ * Event details when a token is highlighted.
*/
-export type TokenHighlightedListener = (
- newHighlight: string | undefined,
- newLineNumber: number,
- hoveredElement?: Element
+export declare interface TokenHighlightEventDetails {
+ token: string;
+ element: Element;
+ side: Side;
+ range: TextRange;
+}
+
+/**
+ * Listens to changes in token highlighting - when a new token starts or stopped
+ * being highlighted. undefined is sent if the event is about a clear in
+ * highlighting.
+ */
+export type TokenHighlightListener = (
+ tokenHighlightEvent?: TokenHighlightEventDetails
) => void;
export declare interface ImageDiffPreferences {
diff --git a/polygerrit-ui/app/api/embed.ts b/polygerrit-ui/app/api/embed.ts
index fed724e..520aeec 100644
--- a/polygerrit-ui/app/api/embed.ts
+++ b/polygerrit-ui/app/api/embed.ts
@@ -24,7 +24,7 @@
DiffLayer,
GrAnnotation,
GrDiffCursor,
- TokenHighlightedListener,
+ TokenHighlightListener,
} from './diff';
declare global {
@@ -35,7 +35,7 @@
TokenHighlightLayer: {
new (
container?: HTMLElement,
- listener?: TokenHighlightedListener
+ listener?: TokenHighlightListener
): DiffLayer;
};
};
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
index 6770c00..d160a28 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
@@ -28,7 +28,6 @@
import '../../plugins/gr-endpoint-param/gr-endpoint-param';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-change-list-item_html';
-import {ChangeTableMixin} from '../../../mixins/gr-change-table-mixin/gr-change-table-mixin';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
import {getDisplayName} from '../../../utils/display-name-util';
import {getPluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints';
@@ -76,11 +75,8 @@
// How many reviewers should be shown with an account-label?
const PRIMARY_REVIEWERS_COUNT = 2;
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = ChangeTableMixin(PolymerElement);
-
@customElement('gr-change-list-item')
-export class GrChangeListItem extends base {
+export class GrChangeListItem extends PolymerElement {
static get template() {
return htmlTemplate;
}
@@ -397,6 +393,13 @@
return change?.attention_set[account._account_id]?.last_update;
}
+ _computeIsColumnHidden(columnToCheck?: string, columnsToDisplay?: string[]) {
+ if (!columnsToDisplay || !columnToCheck) {
+ return false;
+ }
+ return !columnsToDisplay.includes(columnToCheck);
+ }
+
toggleReviewed() {
if (!this.change) return;
const newVal = !this.change?.reviewed;
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_html.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_html.ts
index 1b47791..f0d9268 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_html.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_html.ts
@@ -127,7 +127,7 @@
</td>
<td
class="cell subject"
- hidden$="[[isColumnHidden('Subject', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Subject', visibleChangeTableColumns)]]"
>
<a
title$="[[change.subject]]"
@@ -143,7 +143,7 @@
</td>
<td
class="cell status"
- hidden$="[[isColumnHidden('Status', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Status', visibleChangeTableColumns)]]"
>
<template is="dom-repeat" items="[[statuses]]" as="status">
<div class="comma">,</div>
@@ -155,7 +155,7 @@
</td>
<td
class="cell owner"
- hidden$="[[isColumnHidden('Owner', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Owner', visibleChangeTableColumns)]]"
>
<gr-account-link
highlightAttention
@@ -165,7 +165,7 @@
</td>
<td
class="cell assignee"
- hidden$="[[isColumnHidden('Assignee', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Assignee', visibleChangeTableColumns)]]"
>
<template is="dom-if" if="[[change.assignee]]">
<gr-account-link
@@ -179,7 +179,7 @@
</td>
<td
class="cell reviewers"
- hidden$="[[isColumnHidden('Reviewers', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Reviewers', visibleChangeTableColumns)]]"
>
<div>
<template
@@ -211,7 +211,7 @@
</td>
<td
class="cell comments"
- hidden$="[[isColumnHidden('Comments', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Comments', visibleChangeTableColumns)]]"
>
<iron-icon
hidden$="[[!change.unresolved_comment_count]]"
@@ -221,7 +221,7 @@
</td>
<td
class="cell repo"
- hidden$="[[isColumnHidden('Repo', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Repo', visibleChangeTableColumns)]]"
>
<a class="fullRepo" href$="[[_computeRepoUrl(change)]]">
[[_computeRepoDisplay(change, false)]]
@@ -236,7 +236,7 @@
</td>
<td
class="cell branch"
- hidden$="[[isColumnHidden('Branch', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Branch', visibleChangeTableColumns)]]"
>
<a href$="[[_computeRepoBranchURL(change)]]"> [[change.branch]] </a>
<template is="dom-if" if="[[change.topic]]">
@@ -250,7 +250,7 @@
</td>
<td
class="cell updated"
- hidden$="[[isColumnHidden('Updated', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Updated', visibleChangeTableColumns)]]"
>
<gr-date-formatter
withTooltip
@@ -259,7 +259,7 @@
</td>
<td
class="cell submitted"
- hidden$="[[isColumnHidden('Submitted', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Submitted', visibleChangeTableColumns)]]"
>
<gr-date-formatter
withTooltip
@@ -268,7 +268,7 @@
</td>
<td
class="cell waiting"
- hidden$="[[isColumnHidden('Waiting', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Waiting', visibleChangeTableColumns)]]"
>
<gr-date-formatter
withTooltip
@@ -279,7 +279,7 @@
</td>
<td
class="cell size"
- hidden$="[[isColumnHidden('Size', visibleChangeTableColumns)]]"
+ hidden$="[[_computeIsColumnHidden('Size', visibleChangeTableColumns)]]"
>
<gr-tooltip-content has-tooltip="" title="[[_computeSizeTooltip(change)]]">
<template is="dom-if" if="[[_changeSize]]">
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
index ac0b929..aa04784 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
@@ -29,6 +29,7 @@
TopicName,
} from '../../../types/common';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
+import {columnNames} from '../gr-change-list/gr-change-list';
import './gr-change-list-item';
import {GrChangeListItem, LabelCategory} from './gr-change-list-item';
@@ -372,7 +373,7 @@
await flush();
- for (const column of element.columnNames) {
+ for (const column of columnNames) {
const elementClass = '.' + column.toLowerCase();
assert.isFalse(
queryAndAssert(element, elementClass).hasAttribute('hidden')
@@ -395,7 +396,7 @@
await flush();
- for (const column of element.columnNames) {
+ for (const column of columnNames) {
const elementClass = '.' + column.toLowerCase();
if (column === 'Repo') {
assert.isTrue(
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
index 40674d4..a2a46e0 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
@@ -24,7 +24,6 @@
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-change-list_html';
import {appContext} from '../../../services/app-context';
-import {ChangeTableMixin} from '../../../mixins/gr-change-table-mixin/gr-change-table-mixin';
import {
KeyboardShortcutMixin,
Shortcut,
@@ -57,6 +56,19 @@
const LABEL_PREFIX_INVALID_PROLOG = 'Invalid-Prolog-Rules-Label-Name--';
const MAX_SHORTCUT_CHARS = 5;
+export const columnNames = [
+ 'Subject',
+ 'Status',
+ 'Owner',
+ 'Assignee',
+ 'Reviewers',
+ 'Comments',
+ 'Repo',
+ 'Branch',
+ 'Updated',
+ 'Size',
+];
+
export interface ChangeListSection {
name?: string;
query?: string;
@@ -68,7 +80,7 @@
}
// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = ChangeTableMixin(KeyboardShortcutMixin(PolymerElement));
+const base = KeyboardShortcutMixin(PolymerElement);
@customElement('gr-change-list')
export class GrChangeList extends base {
@@ -219,32 +231,44 @@
return;
}
- this.changeTableColumns = this.columnNames;
+ this.changeTableColumns = columnNames;
this.showNumber = false;
- this.visibleChangeTableColumns = this.getEnabledColumns(
- this.columnNames,
- config,
- this.flagsService.enabledExperiments
+ this.visibleChangeTableColumns = this.changeTableColumns.filter(col =>
+ this._isColumnEnabled(col, config, this.flagsService.enabledExperiments)
);
-
if (account && preferences) {
this.showNumber = !!(
preferences && preferences.legacycid_in_change_table
);
if (preferences.change_table && preferences.change_table.length > 0) {
- const prefColumns = this.renameProjectToRepoColumn(
- preferences.change_table
+ const prefColumns = preferences.change_table.map(column =>
+ column === 'Project' ? 'Repo' : column
);
- this.visibleChangeTableColumns = this.getEnabledColumns(
- prefColumns,
- config,
- this.flagsService.enabledExperiments
+ this.visibleChangeTableColumns = prefColumns.filter(col =>
+ this._isColumnEnabled(
+ col,
+ config,
+ this.flagsService.enabledExperiments
+ )
);
}
}
}
/**
+ * Is the column disabled by a server config or experiment? For example the
+ * assignee feature might be disabled and thus the corresponding column is
+ * also disabled.
+ *
+ */
+ _isColumnEnabled(column: string, config: ServerInfo, experiments: string[]) {
+ if (!config || !config.change) return true;
+ if (column === 'Assignee') return !!config.change.enable_assignee;
+ if (column === 'Comments') return experiments.includes('comments-column');
+ return true;
+ }
+
+ /**
* This methods allows us to customize the columns per section.
*
* @param visibleColumns are the columns according to configs and user prefs
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.js b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.js
index 0d2056a..7b226e7 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.js
@@ -269,7 +269,7 @@
});
test('all columns visible', () => {
- for (const column of element.columnNames) {
+ for (const column of element.changeTableColumns) {
const elementClass = '.' + element._lowerCase(column);
assert.isFalse(element.shadowRoot
.querySelector(elementClass).hidden);
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 ed9820f..3396e13 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
@@ -51,7 +51,7 @@
} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
import {GrEditConstants} from '../../edit/gr-edit-constants';
import {pluralize} from '../../../utils/string-util';
-import {windowLocationReload} from '../../../utils/dom-util';
+import {windowLocationReload, querySelectorAll} from '../../../utils/dom-util';
import {
GeneratedWebLink,
GerritNav,
@@ -84,6 +84,7 @@
isCc,
isOwner,
isReviewer,
+ isInvolved,
} from '../../../utils/change-util';
import {EventType as PluginEventType} from '../../../api/plugin';
import {customElement, observe, property} from '@polymer/decorators';
@@ -188,6 +189,11 @@
changeComments$,
drafts$,
} from '../../../services/comments/comments-model';
+import {
+ hasAttention,
+ getAddedByReason,
+ getRemovedByReason,
+} from '../../../utils/attention-set-util';
const MIN_LINES_FOR_COMMIT_COLLAPSE = 18;
@@ -579,6 +585,7 @@
[Shortcut.DIFF_RIGHT_AGAINST_LATEST]: '_handleDiffRightAgainstLatest',
[Shortcut.DIFF_BASE_AGAINST_LATEST]: '_handleDiffBaseAgainstLatest',
[Shortcut.OPEN_SUBMIT_DIALOG]: '_handleOpenSubmitDialog',
+ [Shortcut.TOGGLE_ATTENTION_SET]: '_handleToggleAttentionSet',
};
}
@@ -1172,6 +1179,9 @@
_paramsChanged(value: AppElementChangeViewParams) {
if (value.view !== GerritView.CHANGE) {
this._initialLoadComplete = false;
+ querySelectorAll(this, 'gr-overlay').forEach(overlay =>
+ (overlay as GrOverlay).close()
+ );
return;
}
@@ -1523,6 +1533,48 @@
this.$.actions.showSubmitDialog();
}
+ _handleToggleAttentionSet(e: CustomKeyboardEvent) {
+ if (this.shouldSuppressKeyboardShortcut(e)) {
+ return;
+ }
+ if (!this._change || !this._account?._account_id) return;
+ if (!this._loggedIn || !isInvolved(this._change, this._account)) return;
+ if (!this._change.attention_set) this._change.attention_set = {};
+ if (hasAttention(this._account, this._change)) {
+ const reason = getRemovedByReason(this._account, this._serverConfig);
+ if (this._change.attention_set)
+ delete this._change.attention_set[this._account._account_id];
+ fireAlert(this, 'Removing you from the attention set ...');
+ this.restApiService
+ .removeFromAttentionSet(
+ this._change._number,
+ this._account._account_id,
+ reason
+ )
+ .then(() => {
+ fireEvent(this, 'hide-alert');
+ });
+ } else {
+ const reason = getAddedByReason(this._account, this._serverConfig);
+ fireAlert(this, 'Adding you to the attention set ...');
+ this._change.attention_set[this._account._account_id!] = {
+ account: this._account,
+ reason,
+ reason_account: this._account,
+ };
+ this.restApiService
+ .addToAttentionSet(
+ this._change._number,
+ this._account._account_id,
+ reason
+ )
+ .then(() => {
+ fireEvent(this, 'hide-alert');
+ });
+ }
+ this._change = {...this._change};
+ }
+
_handleDiffAgainstBase(e: CustomKeyboardEvent) {
if (this.shouldSuppressKeyboardShortcut(e)) {
return;
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
index 676e066..6668e15 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
@@ -59,6 +59,7 @@
createAccountWithIdNameAndEmail,
createChangeViewChange,
createRelatedChangeAndCommitInfo,
+ createAccountDetailWithId,
} from '../../../test/test-data-generators';
import {ChangeViewPatchRange, GrChangeView} from './gr-change-view';
import {
@@ -366,7 +367,9 @@
},
})
);
- stubRestApi('getAccount').returns(Promise.resolve(undefined));
+ stubRestApi('getAccount').returns(
+ Promise.resolve(createAccountDetailWithId(5))
+ );
stubRestApi('getDiffComments').returns(Promise.resolve({}));
stubRestApi('getDiffRobotComments').returns(Promise.resolve({}));
stubRestApi('getDiffDrafts').returns(Promise.resolve({}));
@@ -505,6 +508,39 @@
assert.isNotOk(args[2]);
});
+ test('toggle attention set status', async () => {
+ element._change = {
+ ...createChangeViewChange(),
+ revisions: createRevisions(10),
+ };
+ const addToAttentionSetStub = stubRestApi('addToAttentionSet').returns(
+ Promise.resolve(new Response())
+ );
+
+ const removeFromAttentionSetStub = stubRestApi(
+ 'removeFromAttentionSet'
+ ).returns(Promise.resolve(new Response()));
+ element._patchRange = {
+ basePatchNum: 1 as BasePatchSetNum,
+ patchNum: 3 as RevisionPatchSetNum,
+ };
+ sinon.stub(element, 'shouldSuppressKeyboardShortcut').returns(false);
+
+ assert.isNotOk(element._change.attention_set);
+ await element._getLoggedIn();
+ await element.restApiService.getAccount();
+ element._handleToggleAttentionSet(
+ new CustomEvent('') as CustomKeyboardEvent
+ );
+ assert.isTrue(addToAttentionSetStub.called);
+ assert.isFalse(removeFromAttentionSetStub.called);
+
+ element._handleToggleAttentionSet(
+ new CustomEvent('') as CustomKeyboardEvent
+ );
+ assert.isTrue(removeFromAttentionSetStub.called);
+ });
+
suite('plugins adding to file tab', () => {
setup(async () => {
element._changeNum = TEST_NUMERIC_CHANGE_ID;
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
index fa590a3..fd85117 100644
--- a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
+++ b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
@@ -16,7 +16,7 @@
*/
import '../gr-submit-requirement-hovercard/gr-submit-requirement-hovercard';
import {LitElement, css, html} from 'lit';
-import {customElement, property} from 'lit/decorators';
+import {customElement, property, state} from 'lit/decorators';
import {ParsedChangeInfo} from '../../../types/types';
import {
AccountInfo,
@@ -32,7 +32,14 @@
iconForStatus,
} from '../../../utils/label-util';
import {fontStyles} from '../../../styles/gr-font-styles';
-import {charsOnly} from '../../../utils/string-util';
+import {charsOnly, pluralize} from '../../../utils/string-util';
+import {subscribe} from '../../lit/subscription-controller';
+import {
+ allRunsLatestPatchsetLatestAttempt$,
+ CheckRun,
+} from '../../../services/checks/checks-model';
+import {getResultsOf, hasResultsOf} from '../../../services/checks/checks-util';
+import {Category} from '../../../api/checks';
@customElement('gr-submit-requirements')
export class GrSubmitRequirements extends LitElement {
@@ -45,6 +52,9 @@
@property({type: Boolean})
mutable?: boolean;
+ @state()
+ runs: CheckRun[] = [];
+
static override get styles() {
return [
fontStyles,
@@ -95,10 +105,25 @@
td {
padding: var(--spacing-s);
}
+ .votes-cell {
+ display: flex;
+ }
+ .check-error {
+ margin-right: var(--spacing-l);
+ }
+ .check-error iron-icon {
+ color: var(--error-foreground);
+ vertical-align: top;
+ }
`,
];
}
+ constructor() {
+ super();
+ subscribe(this, allRunsLatestPatchsetLatestAttempt$, x => (this.runs = x));
+ }
+
override render() {
const submit_requirements = (this.change?.submit_requirements ?? []).filter(
req => req.status !== SubmitRequirementStatus.NOT_APPLICABLE
@@ -130,7 +155,12 @@
.text="${requirement.name}"
></gr-limited-text>
</td>
- <td>${this.renderVotes(requirement)}</td>
+ <td>
+ <div class="votes-cell">
+ ${this.renderVotes(requirement)}
+ ${this.renderChecks(requirement)}
+ </div>
+ </td>
</tr>`
)}
</tbody>
@@ -199,6 +229,28 @@
);
}
+ renderChecks(requirement: SubmitRequirementResultInfo) {
+ const requirementLabels = extractAssociatedLabels(requirement);
+ const requirementRuns = this.runs
+ .filter(run => hasResultsOf(run, Category.ERROR))
+ .filter(
+ run => run.labelName && requirementLabels.includes(run.labelName)
+ );
+ const runsCount = requirementRuns.reduce(
+ (sum, run) => sum + getResultsOf(run, Category.ERROR).length,
+ 0
+ );
+ if (runsCount > 0) {
+ return html`<span class="check-error"
+ ><iron-icon icon="gr-icons:error"></iron-icon>${pluralize(
+ runsCount,
+ 'error'
+ )}</span
+ >`;
+ }
+ return;
+ }
+
renderTriggerVotes(submitReqs: SubmitRequirementResultInfo[]) {
const labels = this.change?.labels ?? {};
const allLabels = Object.keys(labels);
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
index c9f07f7..c197599 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
@@ -303,9 +303,6 @@
renderStatusLink() {
const link = this.run.statusLink;
if (!link) return;
- // For COMPLETED we think that the status link are too much clutter.
- // That could be re-considered.
- if (this.run.status !== RunStatus.RUNNING) return;
return html`
<a href="${link}" target="_blank" @click="${this.onLinkClick}"
><iron-icon
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
index 4a6f009..8e70eb9 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
@@ -177,8 +177,8 @@
CHANGE_ID_QUERY: /^\/id\/(I[0-9a-f]{40})$/,
- // Matches /c/<changeNum>/[<basePatchNum>..][<patchNum>][/].
- CHANGE_LEGACY: /^\/c\/(\d+)\/?(((-?\d+|edit)(\.\.(\d+|edit))?))?\/?$/,
+ // Matches /c/<changeNum>/[*][/].
+ CHANGE_LEGACY: /^\/c\/(\d+)\/(.*)$/,
CHANGE_NUMBER_LEGACY: /^\/(\d+)\/?/,
// Matches
@@ -210,10 +210,6 @@
// Matches /c/<project>/+/<changeNum>/[<patchNum|edit>]/<path>,edit[#lineNum]
DIFF_EDIT: /^\/c\/(.+)\/\+\/(\d+)\/(\d+|edit)\/(.+),edit(#\d+)?$/,
- // Matches non-project-relative
- // /c/<changeNum>/[<basePatchNum>..]<patchNum>/<path>.
- DIFF_LEGACY: /^\/c\/(\d+)\/((-?\d+|edit)(\.\.(\d+|edit))?)\/(.+)/,
-
// Matches diff routes using @\d+ to specify a file name (whether or not
// the project name is included).
// eslint-disable-next-line max-len
@@ -287,15 +283,6 @@
type QueryStringItem = [string, string]; // [key, value]
-type GenerateUrlLegacyChangeViewParameters = Omit<
- GenerateUrlChangeViewParameters,
- 'project'
->;
-type GenerateUrlLegacyDiffViewParameters = Omit<
- GenerateUrlDiffViewParameters,
- 'project'
->;
-
interface PatchRangeParams {
patchNum?: PatchSetNum;
basePatchNum?: BasePatchSetNum;
@@ -679,37 +666,6 @@
}
/**
- * Given a set of params without a project, gets the project from the rest
- * API project lookup and then sets the app params.
- */
- _normalizeLegacyRouteParams(
- params: Readonly<
- | GenerateUrlLegacyChangeViewParameters
- | GenerateUrlLegacyDiffViewParameters
- >
- ) {
- if (!params.changeNum) {
- return Promise.resolve();
- }
-
- return this.restApiService
- .getFromProjectLookup(params.changeNum)
- .then(project => {
- // Show a 404 and terminate if the lookup request failed. Attempting
- // to redirect after failing to get the project loops infinitely.
- if (!project) {
- this._show404();
- return;
- }
- const updatedParams:
- | GenerateUrlChangeViewParameters
- | GenerateUrlDiffViewParameters = {...params, project};
- this._normalizePatchRangeParams(updatedParams);
- this._redirect(this._generateUrl(updatedParams));
- });
- }
-
- /**
* Normalizes the params object, and determines if the URL needs to be
* modified to fit the proper schema.
*
@@ -1100,8 +1056,6 @@
this._mapRoute(RoutePattern.CHANGE_LEGACY, '_handleChangeLegacyRoute');
- this._mapRoute(RoutePattern.DIFF_LEGACY, '_handleDiffLegacyRoute');
-
this._mapRoute(RoutePattern.AGREEMENTS, '_handleAgreementsRoute', true);
this._mapRoute(
@@ -1666,41 +1620,26 @@
}
_handleChangeLegacyRoute(ctx: PageContextWithQueryMap) {
- // Parameter order is based on the regex group number matched.
- const params: GenerateUrlLegacyChangeViewParameters = {
- changeNum: Number(ctx.params[0]) as NumericChangeId,
- basePatchNum: convertToPatchSetNum(ctx.params[3]) as BasePatchSetNum,
- patchNum: convertToPatchSetNum(ctx.params[5]),
- view: GerritView.CHANGE,
- querystring: ctx.querystring,
- };
-
- this._normalizeLegacyRouteParams(params);
+ const changeNum = Number(ctx.params[0]) as NumericChangeId;
+ if (!changeNum) {
+ this._show404();
+ return;
+ }
+ this.restApiService.getFromProjectLookup(changeNum).then(project => {
+ // Show a 404 and terminate if the lookup request failed. Attempting
+ // to redirect after failing to get the project loops infinitely.
+ if (!project) {
+ this._show404();
+ return;
+ }
+ this._redirect(`/c/${project}/+/${changeNum}/${ctx.params[1]}`);
+ });
}
_handleLegacyLinenum(ctx: PageContextWithQueryMap) {
this._redirect(ctx.path.replace(LEGACY_LINENUM_PATTERN, '#$1'));
}
- _handleDiffLegacyRoute(ctx: PageContextWithQueryMap) {
- // Parameter order is based on the regex group number matched.
- const params: GenerateUrlLegacyDiffViewParameters = {
- changeNum: Number(ctx.params[0]) as NumericChangeId,
- basePatchNum: convertToPatchSetNum(ctx.params[2]) as BasePatchSetNum,
- patchNum: convertToPatchSetNum(ctx.params[4]),
- path: ctx.params[5],
- view: GerritView.DIFF,
- };
-
- const address = this._parseLineAddress(ctx.hash);
- if (address) {
- params.leftSide = address.leftSide;
- params.lineNum = address.lineNum;
- }
-
- this._normalizeLegacyRouteParams(params);
- }
-
_handleDiffEditRoute(ctx: PageContextWithQueryMap) {
// Parameter order is based on the regex group number matched.
const project = ctx.params[0] as RepoName;
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js
index 4c7855b..b91bf0c 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js
@@ -192,7 +192,6 @@
'_handleDiffRoute',
'_handleDefaultRoute',
'_handleChangeLegacyRoute',
- '_handleDiffLegacyRoute',
'_handleDocumentationRedirectRoute',
'_handleDocumentationSearchRoute',
'_handleDocumentationSearchRedirectRoute',
@@ -536,66 +535,6 @@
});
suite('param normalization', () => {
- let projectLookupStub;
- let generateUrlStub;
-
- setup(() => {
- projectLookupStub = stubRestApi('getFromProjectLookup');
- generateUrlStub = sinon.stub(element, '_generateUrl');
- });
-
- suite('_normalizeLegacyRouteParams', () => {
- let rangeStub;
- let redirectStub;
- let show404Stub;
-
- setup(() => {
- rangeStub = sinon.stub(element, '_normalizePatchRangeParams')
- .returns(Promise.resolve());
- redirectStub = sinon.stub(element, '_redirect');
- show404Stub = sinon.stub(element, '_show404');
- });
-
- test('w/o changeNum', () => {
- projectLookupStub.returns(Promise.resolve('foo/bar'));
- const params = {};
- return element._normalizeLegacyRouteParams(params).then(() => {
- assert.isFalse(generateUrlStub.calledOnce);
- assert.isFalse(projectLookupStub.called);
- assert.isFalse(rangeStub.called);
- assert.isFalse(redirectStub.called);
- assert.isFalse(show404Stub.called);
- });
- });
-
- test('w/ changeNum', () => {
- projectLookupStub.returns(Promise.resolve('foo/bar'));
- const params = {changeNum: 1234};
-
- return element._normalizeLegacyRouteParams(params).then(() => {
- assert.isTrue(generateUrlStub.calledOnce);
- const updatedParams = generateUrlStub.lastCall.args[0];
- assert.isTrue(projectLookupStub.called);
- assert.isTrue(rangeStub.called);
- assert.equal(updatedParams.project, 'foo/bar');
- assert.isTrue(redirectStub.calledOnce);
- assert.isFalse(show404Stub.called);
- });
- });
-
- test('halts on project lookup failure', () => {
- projectLookupStub.returns(Promise.resolve(undefined));
- const params = {changeNum: 1234};
- return element._normalizeLegacyRouteParams(params).then(() => {
- assert.isFalse(generateUrlStub.calledOnce);
- assert.isTrue(projectLookupStub.called);
- assert.isFalse(rangeStub.called);
- assert.isFalse(redirectStub.called);
- assert.isTrue(show404Stub.calledOnce);
- });
- });
- });
-
suite('_normalizePatchRangeParams', () => {
test('range n..n normalizes to n', () => {
const params = {basePatchNum: 4, patchNum: 4};
@@ -1367,58 +1306,19 @@
assert.isTrue(redirectStub.calledWithExactly('/c/12345'));
});
- test('_handleChangeLegacyRoute', () => {
- const normalizeRouteStub = sinon.stub(element,
- '_normalizeLegacyRouteParams');
+ test('_handleChangeLegacyRoute', async () => {
+ stubRestApi('getFromProjectLookup').returns(Promise.resolve('project'));
const ctx = {
params: [
1234, // 0 Change number
- null, // 1 Unused
- null, // 2 Unused
- 6, // 3 Base patch number
- null, // 4 Unused
- 9, // 5 Patch number
+ 'comment/6789',
],
querystring: '',
};
element._handleChangeLegacyRoute(ctx);
- assert.isTrue(normalizeRouteStub.calledOnce);
- assert.deepEqual(normalizeRouteStub.lastCall.args[0], {
- changeNum: 1234,
- basePatchNum: 6,
- patchNum: 9,
- view: GerritView.CHANGE,
- querystring: '',
- });
- });
-
- test('_handleDiffLegacyRoute', () => {
- const normalizeRouteStub = sinon.stub(element,
- '_normalizeLegacyRouteParams');
- const ctx = {
- params: [
- 1234, // 0 Change number
- null, // 1 Unused
- 3, // 2 Base patch number
- null, // 3 Unused
- 8, // 4 Patch number
- 'foo/bar', // 5 Diff path
- ],
- path: '/c/1234/3..8/foo/bar',
- hash: 'b123',
- };
- element._handleDiffLegacyRoute(ctx);
- assert.isFalse(redirectStub.called);
- assert.isTrue(normalizeRouteStub.calledOnce);
- assert.deepEqual(normalizeRouteStub.lastCall.args[0], {
- changeNum: 1234,
- basePatchNum: 3,
- patchNum: 8,
- view: GerritView.DIFF,
- path: 'foo/bar',
- lineNum: 123,
- leftSide: true,
- });
+ await flush();
+ assert.isTrue(redirectStub.calledWithExactly('/c/project/+/1234' +
+ '/comment/6789'));
});
test('_handleLegacyLinenum w/ @321', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts
index 480e26c..de7d007 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts
@@ -15,9 +15,17 @@
* limitations under the License.
*/
import {DiffLayer, DiffLayerListener} from '../../../types/types';
-import {GrDiffLine, Side, TokenHighlightedListener} from '../../../api/diff';
+import {GrDiffLine, Side, TokenHighlightListener} from '../../../api/diff';
+import {assertIsDefined} from '../../../utils/common-util';
import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
import {debounce, DelayedTask} from '../../../utils/async-util';
+
+import {
+ getLineElByChild,
+ getSideByLineEl,
+ getPreviousContentNodes,
+} from '../gr-diff/gr-diff-utils';
+
import {
getLineNumberByChild,
lineNumberToNumber,
@@ -66,7 +74,7 @@
private currentHighlight?: string;
/** Trigger when a new token starts or stoped being highlighted.*/
- private readonly tokenHighlightedListener?: TokenHighlightedListener;
+ private readonly tokenHighlightListener?: TokenHighlightListener;
/**
* The line of the currently highlighted token. We store this in order to
@@ -100,9 +108,9 @@
constructor(
container: HTMLElement = document.documentElement,
- tokenHighlightedListener?: TokenHighlightedListener
+ tokenHighlightListener?: TokenHighlightListener
) {
- this.tokenHighlightedListener = tokenHighlightedListener;
+ this.tokenHighlightListener = tokenHighlightListener;
container.addEventListener('click', e => {
this.handleContainerClick(e);
});
@@ -260,18 +268,42 @@
const oldLineNumber = this.currentHighlightLineNumber;
this.currentHighlight = newHighlight;
this.currentHighlightLineNumber = newLineNumber;
-
- if (this.tokenHighlightedListener) {
- this.tokenHighlightedListener(
- newHighlight,
- newLineNumber,
- newHoveredElement
- );
- }
+ this.triggerTokenHighlightEvent(
+ newHighlight,
+ newLineNumber,
+ newHoveredElement
+ );
this.notifyForToken(oldHighlight, oldLineNumber);
this.notifyForToken(newHighlight, newLineNumber);
}
+ triggerTokenHighlightEvent(
+ token: string | undefined,
+ line: number,
+ element: Element | undefined
+ ) {
+ if (!this.tokenHighlightListener) {
+ return;
+ }
+ if (!token || !element) {
+ this.tokenHighlightListener(undefined);
+ return;
+ }
+ const previousTextLength = getPreviousContentNodes(element)
+ .map(sib => sib.textContent!.length)
+ .reduce((partial_sum, a) => partial_sum + a, 0);
+ const lineEl = getLineElByChild(element);
+ assertIsDefined(lineEl, 'Line element should be found!');
+ const side = getSideByLineEl(lineEl);
+ const range = {
+ start_line: line,
+ start_column: previousTextLength + 1, // 1-based inclusive
+ end_line: line,
+ end_column: previousTextLength + token.length, // 1-based inclusive
+ };
+ this.tokenHighlightListener({token, element, side, range});
+ }
+
getSortedLinesForSide(
lineMapping: Map<string, Set<number>>,
token: string | undefined,
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer_test.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer_test.ts
index 4f44665..2993d35 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer_test.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer_test.ts
@@ -17,7 +17,7 @@
import '../../../test/common-test-setup-karma';
import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
-import {Side} from '../../../api/diff';
+import {Side, TokenHighlightEventDetails} from '../../../api/diff';
import {GrDiffLine, GrDiffLineType} from '../gr-diff/gr-diff-line.js';
import {HOVER_DELAY_MS, TokenHighlightLayer} from './token-highlight-layer';
import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
@@ -66,14 +66,12 @@
let container: HTMLElement;
let listener: MockListener;
let highlighter: TokenHighlightLayer;
- let tokenHighlightingCalls: any[] = [];
+ let tokenHighlightingCalls: {details?: TokenHighlightEventDetails}[] = [];
- function tokenHighlightedListener(
- newHighlight: string | undefined,
- newLineNumber: number,
- hoveredElement?: Element
+ function tokenHighlightListener(
+ highlightDetails?: TokenHighlightEventDetails
) {
- tokenHighlightingCalls.push({newHighlight, newLineNumber, hoveredElement});
+ tokenHighlightingCalls.push({details: highlightDetails});
}
setup(async () => {
@@ -81,7 +79,7 @@
tokenHighlightingCalls = [];
container = document.createElement('div');
document.body.appendChild(container);
- highlighter = new TokenHighlightLayer(container, tokenHighlightedListener);
+ highlighter = new TokenHighlightLayer(container, tokenHighlightListener);
highlighter.addListener((...args) => listener.notify(...args));
});
@@ -107,10 +105,13 @@
const lineId = createLineId();
const template = html`
<div class="line">
- <div data-value=${line} class="lineNum"></div>
- <div id=${lineId} class="line-content">${text}</div>
+ <div data-value=${line} class="lineNum right"></div>
+ <div class="content">
+ <div id=${lineId} class="contentText">${text}</div>
+ </div>
</div>
`;
+
const div = document.createElement('div');
render(template, div);
container.appendChild(div);
@@ -277,19 +278,16 @@
assert.equal(tokenHighlightingCalls.length, 0);
clock.tick(HOVER_DELAY_MS);
assert.equal(tokenHighlightingCalls.length, 1);
- assert.deepEqual(tokenHighlightingCalls[0], {
- newHighlight: 'words',
- newLineNumber: 1,
- hoveredElement: words1,
+ assert.deepEqual(tokenHighlightingCalls[0].details, {
+ token: 'words',
+ side: Side.RIGHT,
+ element: words1,
+ range: {start_line: 1, start_column: 5, end_line: 1, end_column: 9},
});
MockInteractions.click(container);
assert.equal(tokenHighlightingCalls.length, 2);
- assert.deepEqual(tokenHighlightingCalls[1], {
- newHighlight: undefined,
- newLineNumber: 0,
- hoveredElement: undefined,
- });
+ assert.deepEqual(tokenHighlightingCalls[1].details, undefined);
});
test('clicking clears highlight', async () => {
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 fada9cb..7393606 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
@@ -129,6 +129,29 @@
rootId: string;
}
+const VISIBLE_TEXT_NODE_TYPES = [Node.TEXT_NODE, Node.ELEMENT_NODE];
+
+export function getPreviousContentNodes(node?: Node | null) {
+ const sibs = [];
+ while (node) {
+ const {parentNode, previousSibling} = node;
+ const topContentLevel =
+ parentNode &&
+ (parentNode as HTMLElement).classList.contains('contentText');
+ let previousEl: Node | undefined | null;
+ if (previousSibling) {
+ previousEl = previousSibling;
+ } else if (!topContentLevel) {
+ previousEl = parentNode?.previousSibling;
+ }
+ if (previousEl && VISIBLE_TEXT_NODE_TYPES.includes(previousEl.nodeType)) {
+ sibs.push(previousEl);
+ }
+ node = previousEl;
+ }
+ return sibs;
+}
+
export function isThreadEl(node: Node): node is GrDiffThreadElement {
return (
node.nodeType === Node.ELEMENT_NODE &&
diff --git a/polygerrit-ui/app/elements/gr-app-element.ts b/polygerrit-ui/app/elements/gr-app-element.ts
index 46a3e84..b6fe60b 100644
--- a/polygerrit-ui/app/elements/gr-app-element.ts
+++ b/polygerrit-ui/app/elements/gr-app-element.ts
@@ -353,6 +353,7 @@
this.bindShortcut(Shortcut.REFRESH_CHANGE_LIST, 'shift+r:keyup');
this.bindShortcut(Shortcut.EDIT_TOPIC, 't');
this.bindShortcut(Shortcut.OPEN_SUBMIT_DIALOG, 'shift+s');
+ this.bindShortcut(Shortcut.TOGGLE_ATTENTION_SET, 'shift+t');
this.bindShortcut(Shortcut.OPEN_REPLY_DIALOG, 'a:keyup');
this.bindShortcut(Shortcut.OPEN_DOWNLOAD_DIALOG, 'd:keyup');
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts
index d8e43ce..96b1ded 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts
@@ -20,16 +20,13 @@
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-change-table-editor_html';
-import {ChangeTableMixin} from '../../../mixins/gr-change-table-mixin/gr-change-table-mixin';
import {customElement, property, observe} from '@polymer/decorators';
import {ServerInfo} from '../../../types/common';
import {appContext} from '../../../services/app-context';
-
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = ChangeTableMixin(PolymerElement);
+import {columnNames} from '../../change-list/gr-change-list/gr-change-list';
@customElement('gr-change-table-editor')
-export class GrChangeTableEditor extends base {
+export class GrChangeTableEditor extends PolymerElement {
static get template() {
return htmlTemplate;
}
@@ -50,18 +47,33 @@
@observe('serverConfig')
_configChanged(config: ServerInfo) {
- this.defaultColumns = this.getEnabledColumns(
- this.columnNames,
- config,
- this.flagsService.enabledExperiments
+ this.defaultColumns = columnNames.filter(col =>
+ this._isColumnEnabled(col, config, this.flagsService.enabledExperiments)
);
if (!this.displayedColumns) return;
this.displayedColumns = this.displayedColumns.filter(column =>
- this.isColumnEnabled(column, config, this.flagsService.enabledExperiments)
+ this._isColumnEnabled(
+ column,
+ config,
+ this.flagsService.enabledExperiments
+ )
);
}
/**
+ * Is the column disabled by a server config or experiment? For example the
+ * assignee feature might be disabled and thus the corresponding column is
+ * also disabled.
+ *
+ */
+ _isColumnEnabled(column: string, config: ServerInfo, experiments: string[]) {
+ if (!config || !config.change) return true;
+ if (column === 'Assignee') return !!config.change.enable_assignee;
+ if (column === 'Comments') return experiments.includes('comments-column');
+ return true;
+ }
+
+ /**
* Get the list of enabled column names from whichever checkboxes are
* checked (excluding the number checkbox).
*/
@@ -78,6 +90,13 @@
.map(checkbox => checkbox.name);
}
+ _computeIsColumnHidden(columnToCheck?: string, columnsToDisplay?: string[]) {
+ if (!columnsToDisplay || !columnToCheck) {
+ return false;
+ }
+ return !columnsToDisplay.includes(columnToCheck);
+ }
+
/**
* Handle a click on a checkbox container and relay the click to the checkbox it
* contains.
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_html.ts b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_html.ts
index a05ec73..e756a20 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_html.ts
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_html.ts
@@ -74,7 +74,7 @@
type="checkbox"
name="[[item]]"
on-click="_handleTargetClick"
- checked$="[[!isColumnHidden(item, displayedColumns)]]"
+ checked$="[[!_computeIsColumnHidden(item, displayedColumns)]]"
/>
</td>
</tr>
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.ts b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.ts
index 4f61972..4f8d0a0 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.ts
@@ -117,7 +117,7 @@
test('_getDisplayedColumns', () => {
const enabledColumns = columns.filter(column =>
- element.isColumnEnabled(column, element.serverConfig!, [])
+ element._isColumnEnabled(column, element.serverConfig!, [])
);
assert.deepEqual(element._getDisplayedColumns(), enabledColumns);
const input = queryAndAssert<HTMLInputElement>(
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
index de84eae..94333c7 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
@@ -46,7 +46,6 @@
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-settings-view_html';
import {getDocsBaseUrl} from '../../../utils/url-util';
-import {ChangeTableMixin} from '../../../mixins/gr-change-table-mixin/gr-change-table-mixin';
import {customElement, property, observe} from '@polymer/decorators';
import {AppElementParams} from '../../gr-app-types';
import {GrAccountInfo} from '../gr-account-info/gr-account-info';
@@ -75,6 +74,7 @@
EmailStrategy,
TimeFormat,
} from '../../../constants/constants';
+import {columnNames} from '../../change-list/gr-change-list/gr-change-list';
const PREFS_SECTION_FIELDS: Array<keyof PreferencesInput> = [
'changes_per_page',
@@ -138,11 +138,8 @@
};
}
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = ChangeTableMixin(PolymerElement);
-
@customElement('gr-settings-view')
-export class GrSettingsView extends base {
+export class GrSettingsView extends PolymerElement {
static get template() {
return htmlTemplate;
}
@@ -261,8 +258,10 @@
this._localMenu = this._cloneMenu(prefs.my);
this._localChangeTableColumns =
prefs.change_table.length === 0
- ? this.columnNames
- : this.renameProjectToRepoColumn(prefs.change_table);
+ ? columnNames
+ : prefs.change_table.map(column =>
+ column === 'Project' ? 'Repo' : column
+ );
})
);
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 b214be0..fa04860 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -674,7 +674,13 @@
@observe('comment.message')
_commentMessageChanged(message: string) {
- this._messageText = message || '';
+ /*
+ * Only overwrite the message text user has typed if there is no existing
+ * text typed by the user. This prevents the bug where creating another
+ * comment triggered a recomputation of comments and the text written by
+ * the user was lost.
+ */
+ if (!this._messageText) this._messageText = message || '';
}
_messageTextChanged(_: string, oldValue: string) {
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
index 31a1614..b963d4b 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
@@ -217,6 +217,29 @@
assert.isTrue(storageStub.called);
});
+ test('comment message sets messageText only when empty', () => {
+ element.changeNum = 1 as NumericChangeId;
+ element.patchNum = 1 as PatchSetNum;
+ element._messageText = '';
+ element.comment = {
+ author: {
+ name: 'Mr. Peanutbutter',
+ email: 'tenn1sballchaser@aol.com' as EmailAddress,
+ },
+ line: 5,
+ path: 'test',
+ __editing: true,
+ __draft: true,
+ message: 'hello world',
+ };
+ // messageText was empty so overwrite the message now
+ assert.equal(element._messageText, 'hello world');
+
+ element.comment!.message = 'new message';
+ // messageText was already set so do not overwrite it
+ assert.equal(element._messageText, 'hello world');
+ });
+
test('_getPatchNum', () => {
element.side = 'PARENT';
element.patchNum = 1 as PatchSetNum;
diff --git a/polygerrit-ui/app/mixins/gr-change-table-mixin/gr-change-table-mixin.ts b/polygerrit-ui/app/mixins/gr-change-table-mixin/gr-change-table-mixin.ts
deleted file mode 100644
index 9e4608f..0000000
--- a/polygerrit-ui/app/mixins/gr-change-table-mixin/gr-change-table-mixin.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-/**
- * @license
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import {PolymerElement} from '@polymer/polymer';
-import {Constructor} from '../../utils/common-util';
-import {property} from '@polymer/decorators';
-import {ServerInfo} from '../../types/common';
-
-/**
- * @polymer
- * @mixinFunction
- */
-export const ChangeTableMixin = <T extends Constructor<PolymerElement>>(
- superClass: T
-) => {
- /**
- * @polymer
- * @mixinClass
- */
- class Mixin extends superClass {
- @property({type: Array})
- readonly columnNames: string[] = [
- 'Subject',
- 'Status',
- 'Owner',
- 'Assignee',
- 'Reviewers',
- 'Comments',
- 'Repo',
- 'Branch',
- 'Updated',
- 'Size',
- ];
-
- isColumnHidden(columnToCheck?: string, columnsToDisplay?: string[]) {
- if (!columnsToDisplay || !columnToCheck) {
- return false;
- }
- return !columnsToDisplay.includes(columnToCheck);
- }
-
- /**
- * Is the column disabled by a server config or experiment? For example the
- * assignee feature might be disabled and thus the corresponding column is
- * also disabled.
- *
- */
- isColumnEnabled(column: string, config: ServerInfo, experiments: string[]) {
- if (!config || !config.change) return true;
- if (column === 'Assignee') return !!config.change.enable_assignee;
- if (column === 'Comments') return experiments.includes('comments-column');
- return true;
- }
-
- /**
- * @return enabled columns, see isColumnEnabled().
- */
- getEnabledColumns(
- columns: string[],
- config: ServerInfo,
- experiments: string[]
- ) {
- return columns.filter(col =>
- this.isColumnEnabled(col, config, experiments)
- );
- }
-
- /**
- * The Project column was renamed to Repo, but some users may have
- * preferences that use its old name. If that column is found, rename it
- * before use.
- *
- * @return If the column was renamed, returns a new array
- * with the corrected name. Otherwise, it returns the original param.
- */
- renameProjectToRepoColumn(columns: string[]) {
- const projectIndex = columns.indexOf('Project');
- if (projectIndex === -1) {
- return columns;
- }
- const newColumns = [...columns];
- newColumns[projectIndex] = 'Repo';
- return newColumns;
- }
- }
-
- return Mixin as T & Constructor<ChangeTableMixinInterface>;
-};
-
-export interface ChangeTableMixinInterface {
- readonly columnNames: string[];
- isColumnHidden(columnToCheck?: string, columnsToDisplay?: string[]): boolean;
- isColumnEnabled(
- column: string,
- config: ServerInfo,
- experiments: string[]
- ): boolean;
- getEnabledColumns(
- columns: string[],
- config: ServerInfo,
- experiments: string[]
- ): string[];
- renameProjectToRepoColumn(columns: string[]): string[];
-}
diff --git a/polygerrit-ui/app/mixins/gr-change-table-mixin/gr-change-table-mixin_test.js b/polygerrit-ui/app/mixins/gr-change-table-mixin/gr-change-table-mixin_test.js
deleted file mode 100644
index 8bc223f..0000000
--- a/polygerrit-ui/app/mixins/gr-change-table-mixin/gr-change-table-mixin_test.js
+++ /dev/null
@@ -1,81 +0,0 @@
-/**
- * @license
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import '../../test/common-test-setup-karma.js';
-import {ChangeTableMixin} from './gr-change-table-mixin.js';
-import {PolymerElement} from '@polymer/polymer/polymer-element.js';
-
-// This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = ChangeTableMixin(PolymerElement);
-
-class GrChangeTableMixinTestElement extends base {
- static get is() { return 'gr-change-table-mixin-test-element'; }
-}
-
-customElements.define(GrChangeTableMixinTestElement.is,
- GrChangeTableMixinTestElement);
-
-const basicFixture = fixtureFromElement(
- 'gr-change-table-mixin-test-element');
-
-suite('gr-change-table-mixin tests', () => {
- let element;
-
- setup(() => {
- element = basicFixture.instantiate();
- });
-
- test('isColumnHidden', () => {
- const columnToCheck = 'Repo';
- let columnsToDisplay = [
- 'Subject',
- 'Status',
- 'Owner',
- 'Assignee',
- 'Repo',
- 'Branch',
- 'Updated',
- 'Size',
- ];
- assert.isFalse(element.isColumnHidden(columnToCheck, columnsToDisplay));
-
- columnsToDisplay = [
- 'Subject',
- 'Status',
- 'Owner',
- 'Assignee',
- 'Branch',
- 'Updated',
- 'Size',
- ];
- assert.isTrue(element.isColumnHidden(columnToCheck, columnsToDisplay));
- });
-
- test('renameProjectToRepoColumn maps Project to Repo', () => {
- const columns = [
- 'Subject',
- 'Status',
- 'Owner',
- ];
- assert.deepEqual(element.renameProjectToRepoColumn(columns),
- columns.slice(0));
- assert.deepEqual(
- element.renameProjectToRepoColumn(columns.concat(['Project'])),
- columns.slice(0).concat(['Repo']));
- });
-});
-
diff --git a/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts b/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
index 5c4c9ce..9092919 100644
--- a/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
+++ b/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.ts
@@ -151,6 +151,7 @@
TOGGLE_CHANGE_STAR = 'TOGGLE_CHANGE_STAR',
REFRESH_CHANGE_LIST = 'REFRESH_CHANGE_LIST',
OPEN_SUBMIT_DIALOG = 'OPEN_SUBMIT_DIALOG',
+ TOGGLE_ATTENTION_SET = 'TOGGLE_ATTENTION_SET',
OPEN_REPLY_DIALOG = 'OPEN_REPLY_DIALOG',
OPEN_DOWNLOAD_DIALOG = 'OPEN_DOWNLOAD_DIALOG',
@@ -334,6 +335,11 @@
ShortcutSection.ACTIONS,
'Open submit dialog'
);
+_describe(
+ Shortcut.TOGGLE_ATTENTION_SET,
+ ShortcutSection.ACTIONS,
+ 'Toggle attention set status'
+);
_describe(Shortcut.EDIT_TOPIC, ShortcutSection.ACTIONS, 'Add a change topic');
_describe(
Shortcut.DIFF_AGAINST_BASE,
diff --git a/polygerrit-ui/app/utils/attention-set-util.ts b/polygerrit-ui/app/utils/attention-set-util.ts
index 631b633..dcd2863 100644
--- a/polygerrit-ui/app/utils/attention-set-util.ts
+++ b/polygerrit-ui/app/utils/attention-set-util.ts
@@ -16,6 +16,7 @@
*/
import {AccountInfo, ChangeInfo, ServerInfo} from '../types/common';
+import {ParsedChangeInfo} from '../types/types';
import {
getAccountTemplate,
isServiceUser,
@@ -29,7 +30,7 @@
export function hasAttention(
account?: AccountInfo,
- change?: ChangeInfo
+ change?: ChangeInfo | ParsedChangeInfo
): boolean {
return (
canHaveAttention(account) &&
@@ -41,7 +42,7 @@
export function getReason(
config?: ServerInfo,
account?: AccountInfo,
- change?: ChangeInfo
+ change?: ChangeInfo | ParsedChangeInfo
) {
if (!hasAttention(account, change)) return '';
if (change?.attention_set === undefined) return '';
diff --git a/polygerrit-ui/app/utils/change-util.ts b/polygerrit-ui/app/utils/change-util.ts
index c94493b..278e7f3 100644
--- a/polygerrit-ui/app/utils/change-util.ts
+++ b/polygerrit-ui/app/utils/change-util.ts
@@ -215,7 +215,7 @@
}
export function isUploader(
- change?: ChangeInfo,
+ change?: ChangeInfo | ParsedChangeInfo,
account?: AccountInfo
): boolean {
if (!change || !account) return false;
@@ -224,7 +224,7 @@
}
export function isInvolved(
- change?: ChangeInfo,
+ change?: ChangeInfo | ParsedChangeInfo,
account?: AccountInfo
): boolean {
const owner = isOwner(change, account);