Hide service users in attention set related components.

1. Do not show service users in the 'Reviewers' dashboard column.
2. Do not show them in the MODIFY attention set section in the reply
   dialog.
3. Never show an attention icon for them in an account label.

Change-Id: I4b38f8832e05edaa49acd683126d1bd7d5c1c5ff
diff --git a/polygerrit-ui/app/constants/constants.ts b/polygerrit-ui/app/constants/constants.ts
index e642373..cf3deb1 100644
--- a/polygerrit-ui/app/constants/constants.ts
+++ b/polygerrit-ui/app/constants/constants.ts
@@ -197,3 +197,7 @@
   FALSE = 'FALSE',
   INHERITED = 'INHERITED',
 }
+
+export enum AccountTag {
+  SERVICE_USER = 'SERVICE_USER',
+}
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
index c0ce378..65b88191 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
@@ -38,6 +38,7 @@
 import {appContext} from '../../../services/app-context.js';
 import {truncatePath} from '../../../utils/path-list-util.js';
 import {changeStatuses} from '../../../utils/change-util.js';
+import {isServiceUser} from '../../../utils/account-util.js';
 
 const CHANGE_SIZE = {
   XS: 10,
@@ -230,7 +231,8 @@
   _computeReviewers(change) {
     if (!change || !change.reviewers || !change.reviewers.REVIEWER) return [];
     const reviewers = [...change.reviewers.REVIEWER].filter(r =>
-      !change.owner || change.owner._account_id !== r._account_id
+      (!change.owner || change.owner._account_id !== r._account_id) &&
+      !isServiceUser(r)
     );
     reviewers.sort((r1, r2) => {
       if (this.account) {
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index 6ccca94..16d6877 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -39,6 +39,7 @@
 import {fetchChangeUpdates} from '../../../utils/patch-set-util.js';
 import {KeyboardShortcutMixin} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.js';
 import {getDisplayName} from '../../../utils/display-name-util.js';
+import {removeServiceUsers} from '../../../utils/account-util.js';
 
 const STORAGE_DEBOUNCE_INTERVAL_MS = 400;
 
@@ -899,11 +900,15 @@
     if (this._uploader) allAccounts.push(this._uploader);
     if (this._reviewers) allAccounts = [...allAccounts, ...this._reviewers];
     if (this._ccs) allAccounts = [...allAccounts, ...this._ccs];
-    return allAccounts;
+    return removeServiceUsers(allAccounts);
+  }
+
+  _removeServiceUsers(accounts) {
+    return removeServiceUsers(accounts);
   }
 
   _computeShowAttentionCcs(ccs) {
-    return !!ccs && ccs.length > 0;
+    return removeServiceUsers(ccs).length > 0;
   }
 
   _computeUploader(change) {
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_html.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_html.ts
index 9e49deb..c717429 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_html.ts
@@ -423,7 +423,11 @@
       <div class="peopleList">
         <div class="peopleListLabel">Reviewers</div>
         <div>
-          <template is="dom-repeat" items="[[_reviewers]]" as="account">
+          <template
+            is="dom-repeat"
+            items="[[_removeServiceUsers(_reviewers)]]"
+            as="account"
+          >
             <gr-account-label
               account="[[account]]"
               force-attention="[[_computeHasNewAttention(account, _newAttentionSet)]]"
@@ -440,7 +444,11 @@
         <div class="peopleList">
           <div class="peopleListLabel">CC</div>
           <div>
-            <template is="dom-repeat" items="[[_ccs]]" as="account">
+            <template
+              is="dom-repeat"
+              items="[[_removeServiceUsers(_ccs)]]"
+              as="account"
+            >
               <gr-account-label
                 account="[[account]]"
                 force-attention="[[_computeHasNewAttention(account, _newAttentionSet)]]"
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js
index 14421c3..f0483df 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js
@@ -24,6 +24,7 @@
 import {PolymerElement} from '@polymer/polymer/polymer-element.js';
 import {htmlTemplate} from './gr-account-label_html.js';
 import {getDisplayName} from '../../../utils/display-name-util.js';
+import {isServiceUser} from '../../../utils/account-util.js';
 
 /**
  * @extends PolymerElement
@@ -104,7 +105,8 @@
   _isAttentionSetEnabled(config, highlight, account, change) {
     return !!config && !!config.change
         && !!config.change.enable_attention_set
-        && !!highlight && !!change && !!account;
+        && !!highlight && !!change && !!account
+        && !isServiceUser(account);
   }
 
   _computeCancelLeftPadding(
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account.js b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account.js
index a92ae4d..a360cb0 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account.js
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account.js
@@ -26,6 +26,7 @@
 import {PolymerElement} from '@polymer/polymer/polymer-element.js';
 import {htmlTemplate} from './gr-hovercard-account_html.js';
 import {appContext} from '../../../services/app-context.js';
+import {isServiceUser} from '../../../utils/account-util.js';
 
 /** @extends PolymerElement */
 class GrHovercardAccount extends GestureEventListeners(
@@ -95,7 +96,8 @@
   get isAttentionSetEnabled() {
     return !!this._config && !!this._config.change
         && !!this._config.change.enable_attention_set
-        && !!this.highlightAttention && !!this.change && !!this.account;
+        && !!this.highlightAttention && !!this.change && !!this.account
+        && !isServiceUser(this.account);
   }
 
   get hasAttention() {
diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts
index 21e3600..ec2ef9e 100644
--- a/polygerrit-ui/app/types/common.ts
+++ b/polygerrit-ui/app/types/common.ts
@@ -28,6 +28,7 @@
   SubmitType,
   InheritedBooleanInfoConfiguredValue,
   ConfigParameterInfoType,
+  AccountTag,
 } from '../constants/constants';
 
 export type BrandType<T, BrandName extends string> = T &
@@ -197,6 +198,7 @@
   _more_accounts?: boolean; // not set if false
   status?: string; // status message of the account
   inactive?: boolean; // not set if false
+  tags?: AccountTag[];
 }
 
 /**
diff --git a/polygerrit-ui/app/utils/access-util_test.js b/polygerrit-ui/app/utils/access-util_test.js
index d4f5669..209c2ff 100644
--- a/polygerrit-ui/app/utils/access-util_test.js
+++ b/polygerrit-ui/app/utils/access-util_test.js
@@ -18,7 +18,7 @@
 import '../test/common-test-setup-karma.js';
 import {toSortedPermissionsArray} from './access-util.js';
 
-suite('gr-access-behavior tests', () => {
+suite('access-util tests', () => {
   test('toSortedPermissionsArray', () => {
     const rules = {
       'global:Project-Owners': {
diff --git a/polygerrit-ui/app/utils/account-util.ts b/polygerrit-ui/app/utils/account-util.ts
new file mode 100644
index 0000000..6ef3548
--- /dev/null
+++ b/polygerrit-ui/app/utils/account-util.ts
@@ -0,0 +1,27 @@
+/**
+ * @license
+ * Copyright (C) 2020 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 {AccountInfo} from '../types/common';
+import {AccountTag} from '../constants/constants';
+
+export function isServiceUser(account?: AccountInfo): boolean {
+  return !!account?.tags?.includes(AccountTag.SERVICE_USER);
+}
+
+export function removeServiceUsers(accounts?: AccountInfo[]): AccountInfo[] {
+  return accounts?.filter(a => !isServiceUser(a)) || [];
+}
diff --git a/polygerrit-ui/app/utils/account-util_test.js b/polygerrit-ui/app/utils/account-util_test.js
new file mode 100644
index 0000000..0628f2d
--- /dev/null
+++ b/polygerrit-ui/app/utils/account-util_test.js
@@ -0,0 +1,46 @@
+/**
+ * @license
+ * Copyright (C) 2020 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 {isServiceUser, removeServiceUsers} from './account-util.js';
+import {AccountTag} from '../constants/constants.js';
+
+const EMPTY = {};
+const ERNIE = {name: 'Ernie'};
+const KERMIT = {name: 'Kermit', tags: ['FROG']};
+const SERVY = {name: 'Servy', tags: [AccountTag.SERVICE_USER]};
+const BOTTY = {name: 'Botty', tags: [AccountTag.SERVICE_USER]};
+
+suite('account-util tests 3', () => {
+  test('isServiceUser', () => {
+    assert.isFalse(isServiceUser());
+    assert.isFalse(isServiceUser(EMPTY));
+    assert.isFalse(isServiceUser(ERNIE));
+    assert.isFalse(isServiceUser(KERMIT));
+    assert.isTrue(isServiceUser(SERVY));
+    assert.isTrue(isServiceUser(BOTTY));
+  });
+
+  test('removeServiceUsers', () => {
+    assert.sameMembers(removeServiceUsers([]), []);
+    assert.sameMembers(removeServiceUsers([EMPTY, ERNIE, KERMIT]),
+        [EMPTY, ERNIE, KERMIT]);
+    assert.sameMembers(removeServiceUsers([SERVY, BOTTY]), []);
+    assert.sameMembers(removeServiceUsers([EMPTY, SERVY, ERNIE, BOTTY, KERMIT]),
+        [EMPTY, ERNIE, KERMIT]);
+  });
+});