Merge branch 'stable-3.1'

* stable-3.1:
  Bump Bazel version to 3.4.1
  Upgrade bazlets to latest stable-2.16

Change-Id: Ib38c14c0723adc0a4d9f2da173c16023dec3892a
diff --git a/.bazelignore b/.bazelignore
deleted file mode 100644
index 30f1613..0000000
--- a/.bazelignore
+++ /dev/null
@@ -1 +0,0 @@
-eclipse-out
diff --git a/.bazelrc b/.bazelrc
deleted file mode 100644
index 3ae03ff..0000000
--- a/.bazelrc
+++ /dev/null
@@ -1,2 +0,0 @@
-build --workspace_status_command="python ./tools/workspace_status.py"
-test --build_tests_only
diff --git a/.bazelversion b/.bazelversion
deleted file mode 100644
index 47b322c..0000000
--- a/.bazelversion
+++ /dev/null
@@ -1 +0,0 @@
-3.4.1
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/.eslintignore
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..da33ea2
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,165 @@
+{
+  "extends": ["eslint:recommended", "google"],
+  "parserOptions": {
+    "ecmaVersion": 8,
+    "sourceType": "module"
+  },
+  "env": {
+    "browser": true,
+    "es6": true
+  },
+  "globals": {
+    "__dirname": false,
+    "app": false,
+    "page": false,
+    "Polymer": false,
+    "process": false,
+    "require": false,
+    "Gerrit": false,
+    "Promise": false,
+    "assert": false,
+    "test": false,
+    "flushAsynchronousOperations": false
+  },
+  "rules": {
+    "arrow-parens": ["error", "as-needed"],
+    "block-spacing": ["error", "always"],
+    "brace-style": ["error", "1tbs", { "allowSingleLine": true }],
+    "camelcase": "off",
+    "comma-dangle": ["error", {
+      "arrays": "always-multiline",
+      "objects": "always-multiline",
+      "imports": "always-multiline",
+      "exports": "always-multiline",
+      "functions": "never"
+    }],
+    "eol-last": "off",
+    "indent": ["error", 2, {
+      "MemberExpression": 2,
+      "FunctionDeclaration": {"body": 1, "parameters": 2},
+      "FunctionExpression": {"body": 1, "parameters": 2},
+      "CallExpression": {"arguments": 2 },
+      "ArrayExpression": 1,
+      "ObjectExpression": 1,
+      "SwitchCase": 1
+    }],
+    "keyword-spacing": ["error", { "after": true, "before": true }],
+    "lines-between-class-members": ["error", "always"],
+    "max-len": [
+      "error",
+      80,
+      2,
+      {
+        "ignoreComments": true,
+        "ignorePattern": "^import .*;$"
+      }
+    ],
+    "new-cap": ["error", { "capIsNewExceptions": ["Polymer", "LegacyElementMixin", "GestureEventListeners", "LegacyDataMixin"] }],
+    "no-console": "off",
+    "no-multiple-empty-lines": [ "error", { "max": 1 } ],
+    "no-prototype-builtins": "off",
+    "no-redeclare": "off",
+    "no-restricted-syntax": [
+      "error",
+      {
+        "selector": "ExpressionStatement > CallExpression > MemberExpression[object.name='test'][property.name='only']",
+        "message": "Remove test.only."
+      },
+      {
+        "selector": "ExpressionStatement > CallExpression > MemberExpression[object.name='suite'][property.name='only']",
+        "message": "Remove suite.only."
+      }
+    ],
+    "no-undef": "off",
+    "no-useless-escape": "off",
+    "no-var": "error",
+    "object-shorthand": ["error", "always"],
+    "padding-line-between-statements": [
+      "error",
+      {
+        "blankLine": "always",
+        "prev": "class",
+        "next": "*"
+      },
+      {
+        "blankLine": "always",
+        "prev": "*",
+        "next": "class"
+      }
+    ],
+    "prefer-arrow-callback": "error",
+    "prefer-const": "error",
+    "prefer-spread": "error",
+    "quote-props": ["error", "consistent-as-needed"],
+    "semi": [2, "always"],
+    "template-curly-spacing": "error",
+    "valid-jsdoc": "off",
+    "require-jsdoc": 0,
+    "valid-jsdoc": 0,
+    "jsdoc/check-alignment": 2,
+    "jsdoc/check-examples": 0,
+    "jsdoc/check-indentation": 0,
+    "jsdoc/check-param-names": 0,
+    "jsdoc/check-syntax": 0,
+    "jsdoc/check-tag-names": 0,
+    "jsdoc/check-types": 0,
+    "jsdoc/implements-on-classes": 2,
+    "jsdoc/match-description": 0,
+    "jsdoc/newline-after-description": 2,
+    "jsdoc/no-types": 0,
+    "jsdoc/no-undefined-types": 0,
+    "jsdoc/require-description": 0,
+    "jsdoc/require-description-complete-sentence": 0,
+    "jsdoc/require-example": 0,
+    "jsdoc/require-hyphen-before-param-description": 0,
+    "jsdoc/require-jsdoc": 0,
+    "jsdoc/require-param": 0,
+    "jsdoc/require-param-description": 0,
+    "jsdoc/require-param-name": 2,
+    "jsdoc/require-param-type": 2,
+    "jsdoc/require-returns": 0,
+    "jsdoc/require-returns-check": 0,
+    "jsdoc/require-returns-description": 0,
+    "jsdoc/require-returns-type": 2,
+    "jsdoc/valid-types": 2,
+    "jsdoc/require-file-overview": ["error", {
+      "tags": {
+        "license": {
+          "mustExist": true,
+          "preventDuplicates": true
+        }
+      }
+    }],
+    "import/named": 2,
+    "import/no-unresolved": 2,
+    "import/no-self-import": 2,
+    // The no-cycle rule is slow, because it doesn't cache dependencies.
+    // Disable it.
+    "import/no-cycle": 0,
+    "import/no-useless-path-segments": 2,
+    "import/no-unused-modules": 2,
+    "import/no-default-export": 2
+  },
+  "plugins": [
+    "html",
+    "jsdoc",
+    "import"
+  ],
+  "settings": {
+    "html/report-bad-indent": "error"
+  },
+  "overrides": [
+    {
+      "files": ["*_html.js", "*-styles.js", "externs.js"],
+      "rules": {
+        "max-len": "off"
+      }
+    },
+    {
+      "files": ["*.html"],
+      "rules": {
+        "jsdoc/require-file-overview": "off"
+      }
+    }
+  ]
+}
diff --git a/.gitignore b/.gitignore
index a85c827..1c805b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,4 @@
 /.settings
 /local.properties
 *.pyc
-/.primary_build_tool
-/bazel-*
-/eclipse-out
+/package-lock.json
diff --git a/BUILD b/BUILD
index 32b4c8d..6379a0d 100644
--- a/BUILD
+++ b/BUILD
@@ -1,5 +1,7 @@
 load("@rules_java//java:defs.bzl", "java_library")
+load("@npm_bazel_rollup//:index.bzl", "rollup_bundle")
 load("//tools/bzl:junit.bzl", "junit_tests")
+load("//tools/js:eslint.bzl", "eslint")
 load(
     "//tools/bzl:plugin.bzl",
     "PLUGIN_DEPS",
@@ -16,8 +18,8 @@
         "Gerrit-PluginName: reviewers",
         "Gerrit-Module: com.googlesource.gerrit.plugins.reviewers.Module",
     ],
-    resources = glob(["src/main/resources/**/*"]),
     resource_jars = [":rv-reviewers-static"],
+    resources = glob(["src/main/resources/**/*"]),
 )
 
 genrule2(
@@ -34,11 +36,20 @@
 
 polygerrit_plugin(
     name = "rv-reviewers",
-    srcs = glob([
-        "rv-reviewers/*.html",
-        "rv-reviewers/*.js",
-    ]),
-    app = "plugin.html",
+    app = "reviewers-bundle.js",
+    plugin_name = "rv-reviewers",
+)
+
+rollup_bundle(
+    name = "reviewers-bundle",
+    srcs = glob(["rv-reviewers/*.js"]),
+    entry_point = "rv-reviewers/plugin.js",
+    format = "iife",
+    rollup_bin = "//tools/node_tools:rollup-bin",
+    sourcemap = "hidden",
+    deps = [
+        "@tools_npm//rollup-plugin-node-resolve",
+    ],
 )
 
 junit_tests(
@@ -46,16 +57,28 @@
     size = "small",
     srcs = glob(["src/test/java/**/*.java"]),
     tags = ["reviewers"],
-    deps = [
-        ":reviewers__plugin_test_deps",
+    deps = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
+        ":reviewers__plugin",
     ],
 )
 
-java_library(
-    name = "reviewers__plugin_test_deps",
-    testonly = 1,
-    visibility = ["//visibility:public"],
-    exports = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
-        ":reviewers__plugin",
+# Define the eslinter for the plugin
+# The eslint macro creates 2 rules: lint_test and lint_bin
+eslint(
+    name = "lint",
+    srcs = glob([
+        "rv-reviewers/*.js",
+    ]),
+    config = ".eslintrc.json",
+    data = [],
+    extensions = [
+        ".js",
+    ],
+    ignore = ".eslintignore",
+    plugins = [
+        "@npm//eslint-config-google",
+        "@npm//eslint-plugin-html",
+        "@npm//eslint-plugin-import",
+        "@npm//eslint-plugin-jsdoc",
     ],
 )
diff --git a/WORKSPACE b/WORKSPACE
deleted file mode 100644
index 000758e..0000000
--- a/WORKSPACE
+++ /dev/null
@@ -1,41 +0,0 @@
-workspace(name = "reviewers")
-
-load("//:bazlets.bzl", "load_bazlets")
-
-load_bazlets(
-    commit = "b6cb89690a5e06261932eb30c95fd8db34a7943a",
-    #local_path = "/home/<user>/projects/bazlets",
-)
-
-# Polymer dependencies
-load(
-    "@com_googlesource_gerrit_bazlets//:gerrit_polymer.bzl",
-    "gerrit_polymer",
-)
-
-gerrit_polymer()
-
-# Load closure compiler with transitive dependencies
-load("@io_bazel_rules_closure//closure:repositories.bzl", "rules_closure_dependencies", "rules_closure_toolchains")
-
-rules_closure_dependencies()
-
-rules_closure_toolchains()
-
-# Load Gerrit npm_binary toolchain
-load("@com_googlesource_gerrit_bazlets//tools:js.bzl", "GERRIT", "npm_binary")
-
-npm_binary(
-    name = "polymer-bundler",
-    repository = GERRIT,
-)
-
-npm_binary(
-    name = "crisper",
-    repository = GERRIT,
-)
-
-# Load plugin API
-load("@com_googlesource_gerrit_bazlets//:gerrit_api.bzl", "gerrit_api")
-
-gerrit_api()
diff --git a/bazlets.bzl b/bazlets.bzl
deleted file mode 100644
index f089af4..0000000
--- a/bazlets.bzl
+++ /dev/null
@@ -1,18 +0,0 @@
-load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
-
-NAME = "com_googlesource_gerrit_bazlets"
-
-def load_bazlets(
-        commit,
-        local_path = None):
-    if not local_path:
-        git_repository(
-            name = NAME,
-            remote = "https://gerrit.googlesource.com/bazlets",
-            commit = commit,
-        )
-    else:
-        native.local_repository(
-            name = NAME,
-            path = local_path,
-        )
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..2064647
--- /dev/null
+++ b/package.json
@@ -0,0 +1,13 @@
+{
+  "name": "reviewers",
+  "description": "Reviewers plugin",
+  "browser": true,
+  "scripts": {
+    "safe_bazelisk": "if which bazelisk >/dev/null; then bazel_bin=bazelisk; else bazel_bin=bazel; fi && $bazel_bin",
+    "eslint": "npm run safe_bazelisk test :lint_test",
+    "eslintfix": "npm run safe_bazelisk run :lint_bin -- -- --fix $(pwd)"
+  },
+  "devDependencies": {},
+  "license": "Apache-2.0",
+  "private": true
+}
diff --git a/plugin.html b/plugin.html
deleted file mode 100644
index 29c10cb..0000000
--- a/plugin.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-@license
-Copyright (C) 2019 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.
--->
-<link rel="import" href="./rv-reviewers/rv-reviewers.html">
-
-<dom-module id="reviewers">
-  <script>
-    if (window.Polymer) {
-      Gerrit.install(plugin => {
-        plugin.registerCustomComponent('repo-command', 'rv-reviewers');
-      });
-    }
-  </script>
-</dom-module>
diff --git a/rv-reviewers/plugin.js b/rv-reviewers/plugin.js
new file mode 100644
index 0000000..ba7e1d2
--- /dev/null
+++ b/rv-reviewers/plugin.js
@@ -0,0 +1,22 @@
+/**
+ * @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 './rv-reviewers.js';
+
+Gerrit.install(plugin => {
+  plugin.registerCustomComponent(
+      'repo-command', 'rv-reviewers');
+});
diff --git a/rv-reviewers/rv-edit-screen.js b/rv-reviewers/rv-edit-screen.js
index 05e0df8..0c692f5 100644
--- a/rv-reviewers/rv-edit-screen.js
+++ b/rv-reviewers/rv-edit-screen.js
@@ -1,24 +1,38 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-(function() {
-  Polymer({
-    is: 'rv-edit-screen',
+/**
+ * @license
+ * Copyright (C) 2019 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 {htmlTemplate} from './rv-edit-screen_html.js';
 
-    properties: {
+class RvEditScreen extends Polymer.Element {
+  /** @returns {string} name of the component */
+  static get is() { return 'rv-edit-screen'; }
+
+  /** @returns {?} template for this component */
+  static get template() { return htmlTemplate; }
+
+  /**
+   * Defines properties of the component
+   *
+   * @returns {?}
+   */
+  static get properties() {
+    return {
       pluginRestApi: {
         type: Object,
-        observer: '_loadFilterSections'
+        observer: '_loadFilterSections',
       },
       repoName: String,
       loading: Boolean,
@@ -28,41 +42,45 @@
         value: false,
       },
       _filterSections: Array,
-    },
+    };
+  }
 
-    _loadFilterSections() {
-      this.pluginRestApi.get(this._getReviewersUrl(this.repoName))
-          .then(filterSections => {
-            this._filterSections = filterSections;
-          });
-    },
+  _loadFilterSections() {
+    this.pluginRestApi.get(this._getReviewersUrl(this.repoName))
+        .then(filterSections => {
+          this._filterSections = filterSections;
+        });
+  }
 
-    _computeAddFilterBtnHidden(canModifyConfig, editingFilter) {
-      return !canModifyConfig || editingFilter;
-    },
+  _computeAddFilterBtnHidden(canModifyConfig, editingFilter) {
+    return !canModifyConfig || editingFilter;
+  }
 
-    _computeLoadingClass(loading) {
-      return loading ? 'loading' : '';
-    },
+  _computeLoadingClass(loading) {
+    return loading ? 'loading' : '';
+  }
 
-    _getReviewersUrl(repoName) {
-      return `/projects/${encodeURIComponent(repoName)}/reviewers`;
-    },
+  _getReviewersUrl(repoName) {
+    return `/projects/${encodeURIComponent(repoName)}/reviewers`;
+  }
 
-    _handleCreateSection() {
-      const section = {filter: '', reviewers: [], editing: true};
-      this._editingFilter = true;
-      this.push('_filterSections', section);
-    },
+  _handleCreateSection() {
+    const section = {filter: '', reviewers: [], editing: true};
+    this._editingFilter = true;
+    this.push('_filterSections', section);
+  }
 
-    _handleCloseTap(e) {
-      e.preventDefault();
-      this.fire('close', null, {bubbles: false});
-    },
+  _handleCloseTap(e) {
+    e.preventDefault();
+    this.dispatchEvent(new CustomEvent('close', {
+      composed: true, bubbles: false,
+    }));
+  }
 
-    _handleReviewerChanged(e) {
-      this._filterSections = e.detail.result;
-      this._editingFilter = false;
-    },
-  });
-})();
+  _handleReviewerChanged(e) {
+    this._filterSections = e.detail.result;
+    this._editingFilter = false;
+  }
+}
+
+customElements.define(RvEditScreen.is, RvEditScreen);
diff --git a/rv-reviewers/rv-edit-screen.html b/rv-reviewers/rv-edit-screen_html.js
similarity index 70%
rename from rv-reviewers/rv-edit-screen.html
rename to rv-reviewers/rv-edit-screen_html.js
index 3291e3c..63ea346 100644
--- a/rv-reviewers/rv-edit-screen.html
+++ b/rv-reviewers/rv-edit-screen_html.js
@@ -1,26 +1,28 @@
-<!--
-@license
-Copyright (C) 2019 The Android Open Source Project
+/**
+ * @license
+ * Copyright (C) 2019 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 './rv-filter-section.js';
 
-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.
--->
-<link rel="import" href="./rv-filter-section.html">
-
-<dom-module id="rv-edit-screen">
-  <template>
+export const htmlTemplate = Polymer.html`
     <style include="shared-styles"></style>
     <style include="gr-menu-page-styles"></style>
     <style include="gr-subpage-styles">
+      :host {
+        padding: var(--spacing-xl);
+      }
       .bottomButtons {
         display: flex;
       }
@@ -72,6 +74,4 @@
             hidden="[[_computeAddFilterBtnHidden(canModifyConfig, _editingFilter)]]">Add New Filter</gr-button>
       </div>
     </div>
-  </template>
-  <script src="./rv-edit-screen.js"></script>
-</dom-module>
+`;
diff --git a/rv-reviewers/rv-filter-section.js b/rv-reviewers/rv-filter-section.js
index 110c27d..3bf6d06 100644
--- a/rv-reviewers/rv-filter-section.js
+++ b/rv-reviewers/rv-filter-section.js
@@ -1,21 +1,35 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-(function() {
-  Polymer({
-    is: 'rv-filter-section',
+/**
+ * @license
+ * Copyright (C) 2019 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 {htmlTemplate} from './rv-filter-section_html.js';
 
-    properties: {
+class RvFilterSection extends Polymer.Element {
+  /** @returns {string} name of the component */
+  static get is() { return 'rv-filter-section'; }
+
+  /** @returns {?} template for this component */
+  static get template() { return htmlTemplate; }
+
+  /**
+   * Defines properties of the component
+   *
+   * @returns {?}
+   */
+  static get properties() {
+    return {
       pluginRestApi: Object,
       repoName: String,
       reviewers: Array,
@@ -27,73 +41,81 @@
         value: false,
       },
       reviewersUrl: String,
-    },
+    };
+  }
 
-    attached() {
-      this._updateSection();
-    },
+  connectedCallback() {
+    super.connectedCallback();
+    this._updateSection();
+  }
 
-    _updateSection() {
-      this._originalFilter = this.filter;
-    },
+  _updateSection() {
+    this._originalFilter = this.filter;
+  }
 
-    _computeEditing(filter, _originalFilter) {
-      if (_originalFilter === '') {
-        return true;
-      }
-      return filter === '';
-    },
+  _computeEditing(filter, _originalFilter) {
+    if (_originalFilter === '') {
+      return true;
+    }
+    return filter === '';
+  }
 
-    _computeCancelHidden(filter, _originalFilter) {
-      return !this._computeEditing(filter, _originalFilter);
-    },
+  _computeCancelHidden(filter, _originalFilter) {
+    return !this._computeEditing(filter, _originalFilter);
+  }
 
-    _computeAddBtnHidden(canModifyConfig, editingReviewer) {
-      return !(canModifyConfig && !editingReviewer);
-    },
+  _computeAddBtnHidden(canModifyConfig, editingReviewer) {
+    return !(canModifyConfig && !editingReviewer);
+  }
 
-    _computeFilterInputDisabled(canModifyConfig, originalFilter) {
-      return !canModifyConfig || originalFilter !== '';
-    },
+  _computeFilterInputDisabled(canModifyConfig, originalFilter) {
+    return !canModifyConfig || originalFilter !== '';
+  }
 
-    _handleCancel() {
-      this.remove();
-    },
+  _handleCancel() {
+    this.remove();
+  }
 
-    _handleReviewerDeleted(e) {
-      if (e.detail.editing) {
-        this.reviewers.pop();
-        this._editingReviewer = false;
-      } else {
-        const index = e.model.index;
-        const deleted = this.reviewers[index];
-        this._putReviewer(deleted, 'DELETE');
-      }
-    },
-
-    _handleReviewerAdded(e) {
+  _handleReviewerDeleted(e) {
+    if (e.detail.editing) {
+      this.reviewers.pop();
       this._editingReviewer = false;
-      this._putReviewer(e.detail.reviewer, 'ADD').catch(err => {
-        this.fire('show-alert', {message: err});
-        throw err;
-      });
-    },
+    } else {
+      const index = e.model.index;
+      const deleted = this.reviewers[index];
+      this._putReviewer(deleted, 'DELETE');
+    }
+  }
 
-    _putReviewer(reviewer, action) {
-      return this.pluginRestApi.put(this.reviewersUrl, {
-        action,
-        reviewer,
-        filter: this.filter,
-      }).then(result => {
-        const detail = {result};
-        this.dispatchEvent(
-            new CustomEvent('reviewer-changed', {detail, bubbles: true}));
-      });
-    },
+  _handleReviewerAdded(e) {
+    this._editingReviewer = false;
+    this._putReviewer(e.detail.reviewer, 'ADD').catch(err => {
+      this.dispatchEvent(new CustomEvent('show-alert', {
+        detail: {
+          message: err,
+        },
+        composed: true, bubbles: true,
+      }));
+      throw err;
+    });
+  }
 
-    _handleAddReviewer() {
-      this.push('reviewers', '');
-      this._editingReviewer = true;
-    },
-  });
-})();
+  _putReviewer(reviewer, action) {
+    return this.pluginRestApi.put(this.reviewersUrl, {
+      action,
+      reviewer,
+      filter: this.filter,
+    }).then(result => {
+      const detail = {result};
+      this.dispatchEvent(
+          new CustomEvent('reviewer-changed', {detail, bubbles: true}));
+    });
+  }
+
+  _handleAddReviewer() {
+    this.push('reviewers', '');
+    this._editingReviewer = true;
+  }
+}
+
+customElements.define(RvFilterSection.is, RvFilterSection);
diff --git a/rv-reviewers/rv-filter-section.html b/rv-reviewers/rv-filter-section_html.js
similarity index 76%
rename from rv-reviewers/rv-filter-section.html
rename to rv-reviewers/rv-filter-section_html.js
index 7513675..b4a72e4 100644
--- a/rv-reviewers/rv-filter-section.html
+++ b/rv-reviewers/rv-filter-section_html.js
@@ -1,23 +1,22 @@
-<!--
-@license
-Copyright (C) 2019 The Android Open Source Project
+/**
+ * @license
+ * Copyright (C) 2019 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 './rv-reviewer.js';
 
-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.
--->
-<link rel="import" href="./rv-reviewer.html">
-
-<dom-module id="rv-filter-section">
-  <template>
+export const htmlTemplate = Polymer.html`
     <style include="shared-styles">
       :host {
         display: block;
@@ -30,6 +29,9 @@
         align-items: center;
         display: flex;
       }
+      .name gr-button {
+        margin-left: var(--spacing-m);
+      }
       .header {
         align-items: center;
         background: var(--table-header-background-color);
@@ -99,6 +101,4 @@
         </div><!-- reviewers -->
       </div>
     </fieldset>
-  </template>
-  <script src="./rv-filter-section.js"></script>
-</dom-module>
+`;
diff --git a/rv-reviewers/rv-reviewer.js b/rv-reviewers/rv-reviewer.js
index 681a17e..dd516c4 100644
--- a/rv-reviewers/rv-reviewer.js
+++ b/rv-reviewers/rv-reviewer.js
@@ -1,21 +1,35 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-(function() {
-  Polymer({
-    is: 'rv-reviewer',
+/**
+ * @license
+ * Copyright (C) 2019 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 {htmlTemplate} from './rv-reviewer_html.js';
 
-    properties: {
+class RvReviewer extends Polymer.Element {
+  /** @returns {string} name of the component */
+  static get is() { return 'rv-reviewer'; }
+
+  /** @returns {?} template for this component */
+  static get template() { return htmlTemplate; }
+
+  /**
+   * Defines properties of the component
+   *
+   * @returns {?}
+   */
+  static get properties() {
+    return {
       canModifyConfig: Boolean,
       pluginRestAPi: Object,
       repoName: String,
@@ -33,104 +47,107 @@
         type: Boolean,
         computed: '_computeEditing(reviewer, _originalReviewer)',
       },
-    },
+    };
+  }
 
-    attached() {
-      this._originalReviewer = this.reviewer;
-    },
+  connectedCallback() {
+    super.connectedCallback();
+    this._originalReviewer = this.reviewer;
+  }
 
-    _computeEditing(reviewer, _originalReviewer) {
-      if (_originalReviewer === '') {
-        return true;
-      }
-      return reviewer === '';
-    },
+  _computeEditing(reviewer, _originalReviewer) {
+    if (_originalReviewer === '') {
+      return true;
+    }
+    return reviewer === '';
+  }
 
-    _computeDeleteCancel(reviewer, _originalReviewer) {
-      return this._computeEditing(reviewer, _originalReviewer) ?
+  _computeDeleteCancel(reviewer, _originalReviewer) {
+    return this._computeEditing(reviewer, _originalReviewer) ?
       'Cancel' : 'Delete';
-    },
+  }
 
-    _computeHideAddButton(reviewer, _originalReviewer) {
-      return !(this._computeEditing(reviewer, _originalReviewer)
-      && this._reviewerSearchId);
-    },
+  _computeHideAddButton(reviewer, _originalReviewer) {
+    return !(this._computeEditing(reviewer, _originalReviewer)
+    && this._reviewerSearchId);
+  }
 
-    _computeHideDeleteButton(canModifyConfig) {
-      return !canModifyConfig;
-    },
+  _computeHideDeleteButton(canModifyConfig) {
+    return !canModifyConfig;
+  }
 
-    _getReviewerSuggestions(input) {
-      if (input.length === 0) { return Promise.resolve([]); }
-      const promises = [];
-      promises.push(this._getSuggestedGroups(input));
-      promises.push(this._getSuggestedAccounts(input));
-      return Promise.all(promises).then(result => {
-        return result.flat();
-      });
-    },
+  _getReviewerSuggestions(input) {
+    if (input.length === 0) { return Promise.resolve([]); }
+    const promises = [];
+    promises.push(this._getSuggestedGroups(input));
+    promises.push(this._getSuggestedAccounts(input));
+    return Promise.all(promises).then(result => {
+      return result.flat();
+    });
+  }
 
-    _getSuggestedGroups(input) {
-      const suggestUrl = `/groups/?suggest=${input}&p=${this.repoName}`;
-      return this.pluginRestApi.get(suggestUrl).then(groups => {
-        if (!groups) { return []; }
-        const groupSuggestions = [];
-        for (const key in groups) {
-          if (!groups.hasOwnProperty(key)) { continue; }
-          groupSuggestions.push({
-            name: key,
-            value: key,
-          });
-        }
-        return groupSuggestions;
-      });
-    },
-
-    _getSuggestedAccounts(input) {
-      const suggestUrl = `/accounts/?suggest&q=${input}`;
-      return this.pluginRestApi.get(suggestUrl).then(accounts => {
-        const accountSuggestions = [];
-        let nameAndEmail;
-        let value;
-        if (!accounts) { return []; }
-        for (const key in accounts) {
-          if (!accounts.hasOwnProperty(key)) { continue; }
-          if (accounts[key].email) {
-            nameAndEmail = accounts[key].name +
-              ' <' + accounts[key].email + '>';
-          } else {
-            nameAndEmail = accounts[key].name;
-          }
-          if (accounts[key].username) {
-            value = accounts[key].username;
-          } else if (accounts[key].email) {
-            value = accounts[key].email;
-          } else {
-            value = accounts[key]._account_id;
-          }
-          accountSuggestions.push({
-            name: nameAndEmail,
-            value,
-          });
-        }
-        return accountSuggestions;
-      });
-    },
-
-    _handleDeleteCancel() {
-      const detail = {editing: this._editing};
-      if (this._editing) {
-        this.remove();
+  _getSuggestedGroups(input) {
+    const suggestUrl = `/groups/?suggest=${input}&p=${this.repoName}`;
+    return this.pluginRestApi.get(suggestUrl).then(groups => {
+      if (!groups) { return []; }
+      const groupSuggestions = [];
+      for (const key in groups) {
+        if (!groups.hasOwnProperty(key)) { continue; }
+        groupSuggestions.push({
+          name: key,
+          value: key,
+        });
       }
-      this.dispatchEvent(
-          new CustomEvent('reviewer-deleted', {detail, bubbles: true}));
-    },
+      return groupSuggestions;
+    });
+  }
 
-    _handleAddReviewer() {
-      const detail = {reviewer: this._reviewerSearchId};
-      this._originalReviewer = this.reviewer;
-      this.dispatchEvent(
-          new CustomEvent('reviewer-added', {detail, bubbles: true}));
-    },
-  });
-})();
+  _getSuggestedAccounts(input) {
+    const suggestUrl = `/accounts/?suggest&q=${input}`;
+    return this.pluginRestApi.get(suggestUrl).then(accounts => {
+      const accountSuggestions = [];
+      let nameAndEmail;
+      let value;
+      if (!accounts) { return []; }
+      for (const key in accounts) {
+        if (!accounts.hasOwnProperty(key)) { continue; }
+        if (accounts[key].email) {
+          nameAndEmail = accounts[key].name +
+            ' <' + accounts[key].email + '>';
+        } else {
+          nameAndEmail = accounts[key].name;
+        }
+        if (accounts[key].username) {
+          value = accounts[key].username;
+        } else if (accounts[key].email) {
+          value = accounts[key].email;
+        } else {
+          value = accounts[key]._account_id;
+        }
+        accountSuggestions.push({
+          name: nameAndEmail,
+          value,
+        });
+      }
+      return accountSuggestions;
+    });
+  }
+
+  _handleDeleteCancel() {
+    const detail = {editing: this._editing};
+    if (this._editing) {
+      this.remove();
+    }
+    this.dispatchEvent(
+        new CustomEvent('reviewer-deleted', {detail, bubbles: true}));
+  }
+
+  _handleAddReviewer() {
+    const detail = {reviewer: this._reviewerSearchId};
+    this._originalReviewer = this.reviewer;
+    this.dispatchEvent(
+        new CustomEvent('reviewer-added', {detail, bubbles: true}));
+  }
+}
+
+customElements.define(RvReviewer.is, RvReviewer);
\ No newline at end of file
diff --git a/rv-reviewers/rv-reviewer.html b/rv-reviewers/rv-reviewer_html.js
similarity index 66%
rename from rv-reviewers/rv-reviewer.html
rename to rv-reviewers/rv-reviewer_html.js
index 10ab166..0e9becb 100644
--- a/rv-reviewers/rv-reviewer.html
+++ b/rv-reviewers/rv-reviewer_html.js
@@ -1,22 +1,25 @@
-<!--
-@license
-Copyright (C) 2019 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.
--->
-<dom-module id="rv-reviewer">
-  <template>
+/**
+ * @license
+ * Copyright (C) 2019 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.
+ */
+export const htmlTemplate = Polymer.html`
     <style include="shared-styles">
+      :host {
+        display: block;
+        padding: var(--spacing-s) 0;
+      }
       #editReviewerInput {
         display: block;
         width: 250px;
@@ -30,7 +33,7 @@
       #deleteCancelBtn,
       #addBtn,
       #reviewerField {
-          margin-left: 3px;
+        margin-left: var(--spacing-m);
       }
       #reviewerField {
         width: 250px;
@@ -45,11 +48,11 @@
         <span class="value">
             <!--
               TODO:
-              Investigate wether we could reuse gr-account-list.
+              Investigate whether we could reuse gr-account-list.
               If the REST API returns AccountInfo instead of an account
               identifier String we should be able to use gr-account-list(size=1)
               for all reviewers, including those who are non-editable
-              (#reviewerField below) and allign the plugin with how accounts
+              (#reviewerField below) and align the plugin with how accounts
               are displayed in core Gerrit's UI.
             -->
             <gr-autocomplete
@@ -74,6 +77,4 @@
           on-tap="_handleAddReviewer"
           hidden$="[[_computeHideAddButton(reviewer, _originalReviewer)]]">Add</gr-button>
     </div> <!-- reviewerRow -->
-  </template>
-  <script src="./rv-reviewer.js"></script>
-</dom-module>
+`;
\ No newline at end of file
diff --git a/rv-reviewers/rv-reviewers.html b/rv-reviewers/rv-reviewers.html
deleted file mode 100644
index f44aa6f..0000000
--- a/rv-reviewers/rv-reviewers.html
+++ /dev/null
@@ -1,41 +0,0 @@
-<!--
-@license
-Copyright (C) 2019 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.
--->
-<link rel="import" href="./rv-edit-screen.html">
-
-<dom-module id="rv-reviewers">
-  <template>
-    <style include="shared-styles">
-      #rvScreenOverlay {
-        width: 50em;
-        overflow: auto;
-      }
-    </style>
-    <gr-repo-command
-        title="Reviewers Config"
-        on-command-tap="_handleCommandTap">
-    </gr-repo-command>
-    <gr-overlay id="rvScreenOverlay" with-backdrop>
-      <rv-edit-screen
-          plugin-rest-api="[[pluginRestApi]]"
-          repo-name="[[repoName]]"
-          loading="[[_loading]]"
-          can-modify-config="[[_canModifyConfig]]"
-          on-close="_handleRvEditScreenClose"></rv-edit-screen>
-    </gr-overlay>
-  </template>
-  <script src="./rv-reviewers.js"></script>
-</dom-module>
diff --git a/rv-reviewers/rv-reviewers.js b/rv-reviewers/rv-reviewers.js
index e40e03a..9782b8c 100644
--- a/rv-reviewers/rv-reviewers.js
+++ b/rv-reviewers/rv-reviewers.js
@@ -1,21 +1,35 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-(function() {
-  Polymer({
-    is: 'rv-reviewers',
+/**
+ * @license
+ * Copyright (C) 2019 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 {htmlTemplate} from './rv-reviewers_html.js';
 
-    properties: {
+class RvReviewers extends Polymer.Element {
+  /** @returns {string} name of the component */
+  static get is() { return 'rv-reviewers'; }
+
+  /** @returns {?} template for this component */
+  static get template() { return htmlTemplate; }
+
+  /**
+   * Defines properties of the component
+   *
+   * @returns {?}
+   */
+  static get properties() {
+    return {
       pluginRestApi: Object,
       repoName: String,
       _canModifyConfig: {
@@ -34,49 +48,53 @@
         type: Boolean,
         value: false,
       },
-    },
+    };
+  }
 
-    attached() {
-      this.pluginRestApi = this.plugin.restApi();
-      this._setCanModifyConfig();
-    },
+  connectedCallback() {
+    super.connectedCallback();
+    this.pluginRestApi = this.plugin.restApi();
+    this._setCanModifyConfig();
+  }
 
-    _handleCommandTap() {
-      this.$.rvScreenOverlay.open();
-    },
+  _handleCommandTap() {
+    this.$.rvScreenOverlay.open();
+  }
 
-    _handleRvEditScreenClose() {
-      this.$.rvScreenOverlay.close();
-    },
+  _handleRvEditScreenClose() {
+    this.$.rvScreenOverlay.close();
+  }
 
-    _setCanModifyConfig() {
-      const promises = [];
-      promises.push(this._getRepoAccess(this.repoName).then( access => {
-        if (access && access[this.repoName] && access[this.repoName].is_owner) {
-          this._isOwner = true;
-        }
-      }));
-      promises.push(this._getCapabilities().then(capabilities => {
-        if (capabilities['reviewers-modifyReviewersConfig']) {
-          this._hasModifyCapability = true;
-        }
-      }));
-      Promise.all(promises).then(() => {
-        this._loading = false;
-      });
-    },
+  _setCanModifyConfig() {
+    const promises = [];
+    promises.push(this._getRepoAccess(this.repoName).then( access => {
+      if (access && access[this.repoName] && access[this.repoName].is_owner) {
+        this._isOwner = true;
+      }
+    }));
+    promises.push(this._getCapabilities().then(capabilities => {
+      if (capabilities['reviewers-modifyReviewersConfig']) {
+        this._hasModifyCapability = true;
+      }
+    }));
+    Promise.all(promises).then(() => {
+      this._loading = false;
+    });
+  }
 
-    _computeCanModifyConfig(isOwner, hasModifyCapability) {
-      return isOwner || hasModifyCapability;
-    },
+  _computeCanModifyConfig(isOwner, hasModifyCapability) {
+    return isOwner || hasModifyCapability;
+  }
 
-    _getRepoAccess(repoName) {
-      return this.pluginRestApi.get(
-          '/access/?project=' + encodeURIComponent(repoName));
-    },
+  _getRepoAccess(repoName) {
+    return this.pluginRestApi.get(
+        '/access/?project=' + encodeURIComponent(repoName));
+  }
 
-    _getCapabilities() {
-      return this.pluginRestApi.get('/accounts/self/capabilities');
-    },
-  });
-})();
+  _getCapabilities() {
+    return this.pluginRestApi.get('/accounts/self/capabilities');
+  }
+}
+
+customElements.define(RvReviewers.is, RvReviewers);
+
diff --git a/rv-reviewers/rv-reviewers_html.js b/rv-reviewers/rv-reviewers_html.js
new file mode 100644
index 0000000..53a8aec
--- /dev/null
+++ b/rv-reviewers/rv-reviewers_html.js
@@ -0,0 +1,44 @@
+/**
+ * @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 './rv-edit-screen.js';
+
+export const htmlTemplate = Polymer.html`
+    <style include="shared-styles">
+      :host {
+        display: block;
+        margin-bottom: var(--spacing-xxl);
+      }
+      #rvScreenOverlay {
+        width: 50em;
+        overflow: auto;
+      }
+    </style>
+    <h3>Reviewers Config</h3>
+    <gr-button
+      on-click="_handleCommandTap"
+    >
+      Reviewers Config
+    </gr-button>
+    <gr-overlay id="rvScreenOverlay" with-backdrop>
+      <rv-edit-screen
+          plugin-rest-api="[[pluginRestApi]]"
+          repo-name="[[repoName]]"
+          loading="[[_loading]]"
+          can-modify-config="[[_canModifyConfig]]"
+          on-close="_handleRvEditScreenClose"></rv-edit-screen>
+    </gr-overlay>
+`;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/AddReviewers.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/AddReviewers.java
index 43266a2..30568dc 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/AddReviewers.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/AddReviewers.java
@@ -21,53 +21,47 @@
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.util.RequestContext;
-import com.google.gerrit.server.util.ThreadLocalRequestContext;
+import com.google.gerrit.server.util.ManualRequestContext;
+import com.google.gerrit.server.util.OneOffRequestContext;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
 import java.util.ArrayList;
 import java.util.Set;
 
-abstract class AddReviewers implements Runnable {
+/** Adds reviewers to a change. */
+class AddReviewers implements Runnable {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
-  private final ThreadLocalRequestContext tl;
-  protected final GerritApi gApi;
-  protected final IdentifiedUser.GenericFactory identifiedUserFactory;
-  protected final ChangeInfo changeInfo;
+  private final GerritApi gApi;
+  private final OneOffRequestContext requestContext;
+  private final ChangeInfo changeInfo;
+  private final Set<Account.Id> reviewers;
 
-  AddReviewers(
-      ThreadLocalRequestContext tl,
-      GerritApi gApi,
-      IdentifiedUser.GenericFactory identifiedUserFactory,
-      ChangeInfo changeInfo) {
-    this.tl = tl;
-    this.gApi = gApi;
-    this.identifiedUserFactory = identifiedUserFactory;
-    this.changeInfo = changeInfo;
+  interface Factory {
+    AddReviewers create(ChangeInfo changeInfo, Set<Account.Id> reviewers);
   }
 
-  abstract Set<Account.Id> getReviewers();
+  @Inject
+  AddReviewers(
+      GerritApi gApi,
+      OneOffRequestContext requestContext,
+      @Assisted ChangeInfo changeInfo,
+      @Assisted Set<Account.Id> reviewers) {
+    this.gApi = gApi;
+    this.requestContext = requestContext;
+    this.changeInfo = changeInfo;
+    this.reviewers = reviewers;
+  }
 
   @Override
   public void run() {
-    RequestContext old =
-        tl.setContext(
-            new RequestContext() {
-
-              @Override
-              public CurrentUser getUser() {
-                return identifiedUserFactory.create(Account.id(changeInfo.owner._accountId));
-              }
-            });
-    try {
-      addReviewers(getReviewers(), changeInfo);
-    } finally {
-      tl.setContext(old);
+    try (ManualRequestContext ctx =
+        requestContext.openAs(Account.id(changeInfo.owner._accountId))) {
+      addReviewers();
     }
   }
 
-  private void addReviewers(Set<Account.Id> reviewers, ChangeInfo changeInfo) {
+  private void addReviewers() {
     try {
       // TODO(davido): Switch back to using changes API again,
       // when it supports batch mode for adding reviewers
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/AddReviewersByConfiguration.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/AddReviewersByConfiguration.java
deleted file mode 100644
index 788629c..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/AddReviewersByConfiguration.java
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (C) 2013 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.
-
-package com.googlesource.gerrit.plugins.reviewers;
-
-import com.google.gerrit.entities.Account;
-import com.google.gerrit.extensions.api.GerritApi;
-import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.util.ThreadLocalRequestContext;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-import java.util.Set;
-
-class AddReviewersByConfiguration extends AddReviewers {
-  private final Set<Account.Id> reviewers;
-
-  interface Factory {
-    AddReviewersByConfiguration create(ChangeInfo changeInfo, Set<Account.Id> reviewers);
-  }
-
-  @Inject
-  AddReviewersByConfiguration(
-      ThreadLocalRequestContext tl,
-      GerritApi gApi,
-      IdentifiedUser.GenericFactory identifiedUserFactory,
-      @Assisted ChangeInfo changeInfo,
-      @Assisted Set<Account.Id> reviewers) {
-    super(tl, gApi, identifiedUserFactory, changeInfo);
-    this.reviewers = reviewers;
-  }
-
-  @Override
-  Set<Account.Id> getReviewers() {
-    return reviewers;
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/GetReviewers.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/GetReviewers.java
index 61406fa..4aa8602 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/GetReviewers.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/GetReviewers.java
@@ -22,6 +22,10 @@
 import com.google.inject.Singleton;
 import java.util.List;
 
+/**
+ * GET REST end-point for getting all configured {@link ReviewerFilterSection}s of a project, local
+ * and inherited.
+ */
 @Singleton
 class GetReviewers implements RestReadView<ProjectResource> {
   private final ReviewersConfig config;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/ModifyReviewersConfigCapability.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/ModifyReviewersConfigCapability.java
index 4c6cb9a..90c02c0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/ModifyReviewersConfigCapability.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/ModifyReviewersConfigCapability.java
@@ -16,6 +16,7 @@
 
 import com.google.gerrit.extensions.config.CapabilityDefinition;
 
+/** Capability that allows for editing reviewers.config. */
 public class ModifyReviewersConfigCapability extends CapabilityDefinition {
   static final String MODIFY_REVIEWERS_CONFIG = "modifyReviewersConfig";
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/Module.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/Module.java
index efc3614..4bee073 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/Module.java
@@ -47,6 +47,7 @@
 
   @Override
   protected void configure() {
+    bindWorkQueue();
     bind(CapabilityDefinition.class)
         .annotatedWith(Exports.named(MODIFY_REVIEWERS_CONFIG))
         .to(ModifyReviewersConfigCapability.class);
@@ -67,7 +68,7 @@
       DynamicSet.bind(binder(), PrivateStateChangedListener.class).to(Reviewers.class);
     }
 
-    factory(AddReviewersByConfiguration.Factory.class);
+    factory(AddReviewers.Factory.class);
 
     if (enableREST) {
       install(
@@ -76,7 +77,6 @@
             protected void configure() {
               get(PROJECT_KIND, "reviewers").to(GetReviewers.class);
               put(PROJECT_KIND, "reviewers").to(PutReviewers.class);
-              get(PROJECT_KIND, "suggest_reviewers").to(SuggestProjectReviewers.class);
             }
           });
     }
@@ -85,8 +85,12 @@
           @Override
           protected void configure() {
             DynamicSet.bind(binder(), WebUiPlugin.class)
-                .toInstance(new JavaScriptPlugin("rv-reviewers.html"));
+                .toInstance(new JavaScriptPlugin("rv-reviewers.js"));
           }
         });
   }
+
+  protected void bindWorkQueue() {
+    bind(ReviewerWorkQueue.class).to(ReviewerWorkQueue.Scheduled.class);
+  }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/PutReviewers.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/PutReviewers.java
index ccb57b7..5f55db2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/PutReviewers.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/PutReviewers.java
@@ -46,16 +46,22 @@
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 
+/** PUT REST end-point that removes or adds a reveiwer to a {@link ReviewerFilterSection}. */
 @Singleton
 class PutReviewers implements RestModifyView<ProjectResource, Input> {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
-  public static class Input {
+  protected static class Input {
     public Action action;
     public String filter;
     public String reviewer;
   }
 
+  private enum Action {
+    ADD,
+    REMOVE
+  }
+
   private final String pluginName;
   private final ReviewersConfig config;
   private final Provider<MetaDataUpdate.User> metaDataUpdateFactory;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewerFilterSection.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewerFilterSection.java
index 06e7387..a083239 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewerFilterSection.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewerFilterSection.java
@@ -17,6 +17,15 @@
 import java.util.Objects;
 import java.util.Set;
 
+/**
+ * Representation of a filter section in reviewers.config. Example:
+ *
+ * <pre>
+ * [filter "'"]
+ *   reviewer = joe
+ *   reviewer = jane
+ * </pre>
+ */
 class ReviewerFilterSection {
   private final String filter;
   private final Set<String> reviewers;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewerWorkQueue.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewerWorkQueue.java
new file mode 100644
index 0000000..d4241c7
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewerWorkQueue.java
@@ -0,0 +1,46 @@
+// 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.
+
+package com.googlesource.gerrit.plugins.reviewers;
+
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.inject.Inject;
+
+interface ReviewerWorkQueue {
+  void submit(AddReviewers addReviewers);
+
+  static class Scheduled implements ReviewerWorkQueue {
+    private final WorkQueue workQueue;
+
+    @Inject
+    Scheduled(WorkQueue workQueue) {
+      this.workQueue = workQueue;
+    }
+
+    @Override
+    public void submit(AddReviewers addReviewers) {
+      workQueue.getDefaultQueue().submit(addReviewers);
+    }
+  }
+
+  static class Direct implements ReviewerWorkQueue {
+
+    @Override
+    public void submit(AddReviewers addReviewers) {
+      directExecutor().execute(addReviewers);
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/Reviewers.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/Reviewers.java
index fcf4790..04e301b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/Reviewers.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/Reviewers.java
@@ -36,7 +36,6 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.change.ReviewerSuggestion;
 import com.google.gerrit.server.change.SuggestedReviewer;
-import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
 import com.google.gerrit.server.query.change.InternalChangeQuery;
 import com.google.inject.Inject;
@@ -44,8 +43,8 @@
 import com.google.inject.Singleton;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.Future;
 
+/** Handles automatic adding of reviewers and reviewer suggestions. */
 @Singleton
 class Reviewers
     implements RevisionCreatedListener,
@@ -55,8 +54,8 @@
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
   private final ReviewersResolver resolver;
-  private final AddReviewersByConfiguration.Factory byConfigFactory;
-  private final WorkQueue workQueue;
+  private final AddReviewers.Factory addReviewersFactory;
+  private final ReviewerWorkQueue workQueue;
   private final ReviewersConfig config;
   private final Provider<CurrentUser> user;
   private final ChangeQueryBuilder queryBuilder;
@@ -65,14 +64,14 @@
   @Inject
   Reviewers(
       ReviewersResolver resolver,
-      AddReviewersByConfiguration.Factory byConfigFactory,
-      WorkQueue workQueue,
+      AddReviewers.Factory addReviewersFactory,
+      ReviewerWorkQueue workQueue,
       ReviewersConfig config,
       Provider<CurrentUser> user,
       ChangeQueryBuilder queryBuilder,
       Provider<InternalChangeQuery> queryProvider) {
     this.resolver = resolver;
-    this.byConfigFactory = byConfigFactory;
+    this.addReviewersFactory = addReviewersFactory;
     this.workQueue = workQueue;
     this.config = config;
     this.user = user;
@@ -134,10 +133,11 @@
 
   private void onEvent(ChangeEvent event) {
     ChangeInfo c = event.getChange();
-    if (config.ignoreWip() && (c.workInProgress != null && c.workInProgress)) {
+    /* Never add reviewers automatically to private changes. */
+    if (Boolean.TRUE.equals(c.isPrivate)) {
       return;
     }
-    if (config.ignorePrivate() && (c.isPrivate != null && c.isPrivate)) {
+    if (config.ignoreWip() && Boolean.TRUE.equals(c.workInProgress)) {
       return;
     }
     Project.NameKey projectName = Project.nameKey(c.project);
@@ -155,12 +155,10 @@
       if (reviewers.isEmpty()) {
         return;
       }
-      final Runnable task =
-          byConfigFactory.create(
+      final AddReviewers addReviewers =
+          addReviewersFactory.create(
               c, resolver.resolve(reviewers, projectName, changeNumber, uploader));
-
-      @SuppressWarnings("unused")
-      Future<?> ignored = workQueue.getDefaultQueue().submit(task);
+      workQueue.submit(addReviewers);
     } catch (QueryParseException e) {
       logger.atWarning().log(
           "Could not add default reviewers for change %d of project %s, filter is invalid: %s",
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewersConfig.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewersConfig.java
index 7c61c70..96a4f6b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewersConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewersConfig.java
@@ -34,6 +34,7 @@
 import org.eclipse.jgit.lib.CommitBuilder;
 import org.eclipse.jgit.lib.Config;
 
+/** Global and project local configurations. */
 @Singleton
 public class ReviewersConfig {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -44,7 +45,6 @@
   private static final String KEY_ENABLE_REST = "enableREST";
   private static final String KEY_SUGGEST_ONLY = "suggestOnly";
   private static final String KEY_IGNORE_WIP = "ignoreWip";
-  private static final String KEY_IGNORE_PRIVATE = "ignorePrivate";
 
   private final PluginConfigFactory cfgFactory;
   private final String pluginName;
@@ -52,7 +52,6 @@
   private final boolean enableREST;
   private final boolean suggestOnly;
   private final boolean ignoreWip;
-  private final boolean ignorePrivate;
 
   @Inject
   ReviewersConfig(PluginConfigFactory cfgFactory, @PluginName String pluginName) {
@@ -62,7 +61,6 @@
     this.enableREST = cfg.getBoolean(pluginName, null, KEY_ENABLE_REST, true);
     this.suggestOnly = cfg.getBoolean(pluginName, null, KEY_SUGGEST_ONLY, false);
     this.ignoreWip = cfg.getBoolean(pluginName, null, KEY_IGNORE_WIP, true);
-    this.ignorePrivate = cfg.getBoolean(pluginName, null, KEY_IGNORE_PRIVATE, true);
   }
 
   public ForProject forProject(Project.NameKey projectName) {
@@ -88,10 +86,6 @@
     return ignoreWip;
   }
 
-  public boolean ignorePrivate() {
-    return ignorePrivate;
-  }
-
   static class ForProject extends VersionedMetaData {
     private Config cfg;
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewersResolver.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewersResolver.java
index 0d00272..fc5d9e0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewersResolver.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewersResolver.java
@@ -36,7 +36,11 @@
 import java.util.Set;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 
-/* Resolve account and group names to account ids */
+/**
+ * Attempts to resolve string identifiers in reviewers.config into valid {@link
+ * com.google.gerrit.entities.Account.Id}s when string identifies an account and groups that are
+ * expanded into {@link com.google.gerrit.entities.Account.Id}s if it identifies a group.
+ */
 @Singleton
 class ReviewersResolver {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/SuggestProjectReviewers.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/SuggestProjectReviewers.java
deleted file mode 100644
index 8c2e1ee..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/SuggestProjectReviewers.java
+++ /dev/null
@@ -1,73 +0,0 @@
-// 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.
-
-package com.googlesource.gerrit.plugins.reviewers;
-
-import com.google.gerrit.entities.Account;
-import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.extensions.client.ReviewerState;
-import com.google.gerrit.extensions.common.AccountVisibility;
-import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
-import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.Response;
-import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.permissions.PermissionBackend;
-import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.permissions.ProjectPermission;
-import com.google.gerrit.server.project.ProjectResource;
-import com.google.gerrit.server.restapi.change.ReviewersUtil;
-import com.google.gerrit.server.restapi.change.ReviewersUtil.VisibilityControl;
-import com.google.gerrit.server.restapi.change.SuggestReviewers;
-import com.google.inject.Inject;
-import java.io.IOException;
-import java.util.List;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Config;
-
-public class SuggestProjectReviewers extends SuggestReviewers
-    implements RestReadView<ProjectResource> {
-  private final PermissionBackend permissionBackend;
-
-  @Inject
-  SuggestProjectReviewers(
-      AccountVisibility av,
-      @GerritServerConfig Config cfg,
-      ReviewersUtil reviewersUtil,
-      PermissionBackend permissionBackend) {
-    super(av, cfg, reviewersUtil);
-    this.permissionBackend = permissionBackend;
-  }
-
-  @Override
-  public Response<List<SuggestedReviewerInfo>> apply(ProjectResource rsrc)
-      throws BadRequestException, StorageException, IOException, ConfigInvalidException,
-          PermissionBackendException {
-    return Response.ok(
-        reviewersUtil.suggestReviewers(
-            ReviewerState.REVIEWER, null, this, rsrc.getProjectState(), getVisibility(rsrc), true));
-  }
-
-  private VisibilityControl getVisibility(final ProjectResource rsrc) {
-    return new VisibilityControl() {
-      @Override
-      public boolean isVisibleTo(Account.Id account) {
-        return permissionBackend
-            .absentUser(account)
-            .project(rsrc.getNameKey())
-            .testOrFalse(ProjectPermission.ACCESS);
-      }
-    };
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/Action.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/TestModule.java
similarity index 69%
rename from src/main/java/com/googlesource/gerrit/plugins/reviewers/Action.java
rename to src/main/java/com/googlesource/gerrit/plugins/reviewers/TestModule.java
index 04a15fc..380280a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/Action.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/TestModule.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2014 The Android Open Source Project
+// 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.
@@ -14,7 +14,14 @@
 
 package com.googlesource.gerrit.plugins.reviewers;
 
-public enum Action {
-  ADD,
-  REMOVE
+public class TestModule extends Module {
+
+  public TestModule() {
+    super(true, false);
+  }
+
+  @Override
+  protected void bindWorkQueue() {
+    bind(ReviewerWorkQueue.class).to(ReviewerWorkQueue.Direct.class);
+  }
 }
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index b678ff5..9de1838 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -3,6 +3,11 @@
 The configuration for adding reviewers to submitted changes can be
 [configured per project](config.md).
 
+__NOTE__:
+Private changes are ignored, which means that this plugin will never add reviewers
+to a change that is in `private` state. Reviewers will only be added to such a
+change if it transitions out of `private` state.
+
 SEE ALSO
 --------
 
diff --git a/src/main/resources/Documentation/build.md b/src/main/resources/Documentation/build.md
index de99226..3a4437f 100644
--- a/src/main/resources/Documentation/build.md
+++ b/src/main/resources/Documentation/build.md
@@ -1,37 +1,12 @@
 Build
 =====
 
-This plugin is built with Bazel.
+This plugin can be built with Bazel in the Gerrit tree.
 
-Two build modes are supported: Standalone and in Gerrit tree.
-The standalone build mode is recommended, as this mode doesn't require
-the Gerrit tree to exist locally.
+Clone or link this plugin to the plugins directory of Gerrit's
+source tree.
 
-### Build standalone
-
-```
-  bazel build @PLUGIN@
-```
-
-The output is created in
-
-```
-  bazel-bin/@PLUGIN@.jar
-```
-
-To execute the tests run:
-
-```
-  bazel test //...
-```
-
-This project can be imported into the Eclipse IDE:
-
-```
-  ./tools/eclipse/project.sh
-```
-
-### Build in Gerrit tree
+From the Gerrit source tree issue the command:
 
 ```
   bazel build plugins/@PLUGIN@
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 0b94f0e..1088535 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -9,7 +9,6 @@
     enableREST = true
     suggestOnly = false
     ignoreWip = false
-    ignorePrivate = false
 ```
 
 reviewers.enableREST
@@ -29,10 +28,6 @@
 	considered when adding reviewers. Defaults to true. To enable adding
 	reviewers on changes in WIP state set this value to false.
 
-reviewers.ignorePrivate
-:	Ignore changes in private state. When set to true changes in private state
-	are not considered when adding reviewers. Defaults to true. To enable
-	adding reviewers on changes in Private state set this value to false.
 Per project configuration of the @PLUGIN@ plugin is done in the
 `reviewers.config` file of the project. Missing values are inherited
 from the parent projects. This means a global default configuration can
diff --git a/src/test/java/com/googlesource/gerrit/plugins/reviewers/AbstractReviewersPluginTest.java b/src/test/java/com/googlesource/gerrit/plugins/reviewers/AbstractReviewersPluginTest.java
new file mode 100644
index 0000000..a8b308a
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/reviewers/AbstractReviewersPluginTest.java
@@ -0,0 +1,90 @@
+// 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.
+
+package com.googlesource.gerrit.plugins.reviewers;
+
+import static com.google.gerrit.acceptance.GitUtil.fetch;
+import static com.googlesource.gerrit.plugins.reviewers.ReviewersConfig.FILENAME;
+import static com.googlesource.gerrit.plugins.reviewers.ReviewersConfig.KEY_REVIEWER;
+import static com.googlesource.gerrit.plugins.reviewers.ReviewersConfig.SECTION_FILTER;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.entities.RefNames;
+import com.google.inject.Inject;
+import java.util.Arrays;
+import java.util.List;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Ignore;
+
+/** Base class for reviewer plugin tests. */
+@Ignore
+public class AbstractReviewersPluginTest extends LightweightPluginDaemonTest {
+  @Inject protected ProjectOperations projectOperations;
+
+  protected void createFilters(FilterData... filters) throws Exception {
+    createFiltersFor(testRepo, filters);
+  }
+
+  protected void createFiltersFor(TestRepository<?> repo, FilterData... filters) throws Exception {
+    String previousHead = repo.getRepository().getBranch();
+    checkoutRefsMetaConfig(repo);
+    Config cfg = new Config();
+    Arrays.stream(filters)
+        .forEach(f -> cfg.setStringList(SECTION_FILTER, f.filter, KEY_REVIEWER, f.reviewers));
+    pushFactory
+        .create(admin.newIdent(), repo, "Add reviewers", FILENAME, cfg.toText())
+        .to(RefNames.REFS_CONFIG)
+        .assertOkStatus();
+    repo.reset(previousHead);
+  }
+
+  protected TestRepository<?> checkoutRefsMetaConfig(TestRepository<?> repo) throws Exception {
+    fetch(repo, RefNames.REFS_CONFIG + ":refs/heads/config");
+    repo.reset("refs/heads/config");
+    return repo;
+  }
+
+  protected FilterData filter(String filter) {
+    return new FilterData(filter);
+  }
+
+  /** Assists tests to define a filter. */
+  protected static class FilterData {
+    List<String> reviewers;
+    String filter;
+
+    FilterData(String filter) {
+      this.filter = filter;
+      this.reviewers = Lists.newArrayList();
+    }
+
+    FilterData reviewer(TestAccount reviewer) {
+      return reviewer(reviewer.email());
+    }
+
+    FilterData reviewer(String reviewer) {
+      reviewers.add(reviewer);
+      return this;
+    }
+
+    public ReviewerFilterSection asSection() {
+      return new ReviewerFilterSection(filter, ImmutableSet.copyOf(reviewers));
+    }
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersConfigIT.java b/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersConfigIT.java
index 1a280e2..19bb268 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersConfigIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersConfigIT.java
@@ -14,97 +14,60 @@
 
 package com.googlesource.gerrit.plugins.reviewers;
 
+import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.acceptance.GitUtil.fetch;
-import static com.googlesource.gerrit.plugins.reviewers.ReviewersConfig.FILENAME;
-import static com.googlesource.gerrit.plugins.reviewers.ReviewersConfig.KEY_REVIEWER;
-import static com.googlesource.gerrit.plugins.reviewers.ReviewersConfig.SECTION_FILTER;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.TestPlugin;
-import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.entities.Project;
-import com.google.gerrit.entities.RefNames;
-import com.google.inject.Inject;
+import java.util.Arrays;
 import org.eclipse.jgit.junit.TestRepository;
-import org.eclipse.jgit.lib.Config;
 import org.junit.Before;
 import org.junit.Test;
 
 @NoHttpd
 @TestPlugin(name = "reviewers", sysModule = "com.googlesource.gerrit.plugins.reviewers.Module")
-public class ReviewersConfigIT extends LightweightPluginDaemonTest {
+public class ReviewersConfigIT extends AbstractReviewersPluginTest {
   private static final String BRANCH_MAIN = "branch:main";
   private static final String NO_FILTER = "*";
   private static final String JANE_DOE = "jane.doe@example.com";
   private static final String JOHN_DOE = "john.doe@example.com";
 
-  @Inject private ProjectOperations projectOperations;
-
   @Before
   public void setUp() throws Exception {
-    fetch(testRepo, RefNames.REFS_CONFIG + ":refs/heads/config");
-    testRepo.reset("refs/heads/config");
+    checkoutRefsMetaConfig(testRepo);
   }
 
   @Test
   public void reviewersConfigSingle() throws Exception {
-    Config cfg = new Config();
-    cfg.setString(SECTION_FILTER, NO_FILTER, KEY_REVIEWER, JOHN_DOE);
-    cfg.setString(SECTION_FILTER, BRANCH_MAIN, KEY_REVIEWER, JANE_DOE);
+    createFilters(filter(NO_FILTER).reviewer(JOHN_DOE), filter(BRANCH_MAIN).reviewer(JANE_DOE));
 
-    pushFactory
-        .create(admin.newIdent(), testRepo, "Add reviewers", FILENAME, cfg.toText())
-        .to(RefNames.REFS_CONFIG)
-        .assertOkStatus();
-
-    assertThat(reviewersConfig().forProject(project).getReviewerFilterSections())
-        .containsExactlyElementsIn(
-            ImmutableList.of(
-                new ReviewerFilterSection(NO_FILTER, ImmutableSet.of(JOHN_DOE)),
-                new ReviewerFilterSection(BRANCH_MAIN, ImmutableSet.of(JANE_DOE))))
-        .inOrder();
+    assertProjectHasFilters(
+        project, filter(NO_FILTER).reviewer(JOHN_DOE), filter(BRANCH_MAIN).reviewer(JANE_DOE));
   }
 
   @Test
   public void reviewersConfigWithMergedInheritance() throws Exception {
-    Config parentCfg = new Config();
-    parentCfg.setString(SECTION_FILTER, NO_FILTER, KEY_REVIEWER, JOHN_DOE);
-    parentCfg.setString(SECTION_FILTER, BRANCH_MAIN, KEY_REVIEWER, JOHN_DOE);
-
-    pushFactory
-        .create(
-            admin.newIdent(),
-            testRepo,
-            "Add reviewers parent project",
-            FILENAME,
-            parentCfg.toText())
-        .to(RefNames.REFS_CONFIG)
-        .assertOkStatus();
-
     Project.NameKey childProject = projectOperations.newProject().parent(project).create();
-    TestRepository<?> childTestRepo = cloneProject(childProject);
-    fetch(childTestRepo, RefNames.REFS_CONFIG + ":refs/heads/config");
-    childTestRepo.reset("refs/heads/config");
+    TestRepository<?> childTestRepo = checkoutRefsMetaConfig(cloneProject(childProject));
 
-    Config cfg = new Config();
-    cfg.setString(SECTION_FILTER, NO_FILTER, KEY_REVIEWER, JANE_DOE);
-    cfg.setString(SECTION_FILTER, BRANCH_MAIN, KEY_REVIEWER, JANE_DOE);
+    createFiltersFor(
+        childTestRepo,
+        filter(NO_FILTER).reviewer(JANE_DOE),
+        filter(BRANCH_MAIN).reviewer(JANE_DOE));
 
-    pushFactory
-        .create(
-            admin.newIdent(), childTestRepo, "Add reviewers child project", FILENAME, cfg.toText())
-        .to(RefNames.REFS_CONFIG)
-        .assertOkStatus();
+    createFilters(filter(NO_FILTER).reviewer(JOHN_DOE), filter(BRANCH_MAIN).reviewer(JOHN_DOE));
 
-    assertThat(reviewersConfig().forProject(childProject).getReviewerFilterSections())
+    assertProjectHasFilters(
+        childProject,
+        filter(NO_FILTER).reviewer(JOHN_DOE).reviewer(JANE_DOE),
+        filter(BRANCH_MAIN).reviewer(JOHN_DOE).reviewer(JANE_DOE));
+  }
+
+  private void assertProjectHasFilters(Project.NameKey project, FilterData... filters) {
+    assertThat(reviewersConfig().forProject(project).getReviewerFilterSections())
         .containsExactlyElementsIn(
-            ImmutableList.of(
-                new ReviewerFilterSection(NO_FILTER, ImmutableSet.of(JOHN_DOE, JANE_DOE)),
-                new ReviewerFilterSection(BRANCH_MAIN, ImmutableSet.of(JOHN_DOE, JANE_DOE))))
+            Arrays.stream(filters).map(f -> f.asSection()).collect(toImmutableList()))
         .inOrder();
   }
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersIT.java b/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersIT.java
index 6d31562..a2a7035 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersIT.java
@@ -15,178 +15,111 @@
 package com.googlesource.gerrit.plugins.reviewers;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-import static com.google.gerrit.acceptance.GitUtil.fetch;
 import static com.google.gerrit.extensions.client.ReviewerState.REVIEWER;
-import static com.googlesource.gerrit.plugins.reviewers.ReviewersConfig.FILENAME;
-import static com.googlesource.gerrit.plugins.reviewers.ReviewersConfig.KEY_REVIEWER;
-import static com.googlesource.gerrit.plugins.reviewers.ReviewersConfig.SECTION_FILTER;
 import static java.util.stream.Collectors.toSet;
 
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.acceptance.TestPlugin;
-import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.BranchNameKey;
-import com.google.gerrit.entities.RefNames;
-import com.google.gerrit.extensions.common.AccountInfo;
-import com.google.inject.Inject;
-import java.util.Collection;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.junit.Before;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.client.ChangeStatus;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import java.util.Set;
 import org.junit.Test;
 
 @NoHttpd
-@TestPlugin(name = "reviewers", sysModule = "com.googlesource.gerrit.plugins.reviewers.Module")
-public class ReviewersIT extends LightweightPluginDaemonTest {
-  @Inject private ProjectOperations projectOperations;
-
-  @Before
-  public void setUp() throws Exception {
-    fetch(testRepo, RefNames.REFS_CONFIG + ":refs/heads/config");
-    testRepo.reset("refs/heads/config");
-  }
+@TestPlugin(name = "reviewers", sysModule = "com.googlesource.gerrit.plugins.reviewers.TestModule")
+public class ReviewersIT extends AbstractReviewersPluginTest {
 
   @Test
   public void addReviewers() throws Exception {
-    RevCommit oldHead = projectOperations.project(project).getHead("master");
     TestAccount user2 = accountCreator.user2();
-
-    Config cfg = new Config();
-    cfg.setStringList(
-        SECTION_FILTER, "*", KEY_REVIEWER, ImmutableList.of(user.email(), user2.email()));
-
-    pushFactory
-        .create(admin.newIdent(), testRepo, "Add reviewers", FILENAME, cfg.toText())
-        .to(RefNames.REFS_CONFIG)
-        .assertOkStatus();
-
-    testRepo.reset(oldHead);
+    createFilters(filter("*").reviewer(user).reviewer(user2));
     String changeId = createChange().getChangeId();
-
-    Collection<AccountInfo> reviewers;
-    // Wait for 100 ms until the create patch set event
-    // is processed by the reviewers plugin
-    long wait = 0;
-    do {
-      reviewers = gApi.changes().id(changeId).get().reviewers.get(REVIEWER);
-      if (reviewers == null) {
-        Thread.sleep(10);
-        wait += 10;
-        if (wait > 100) {
-          assertWithMessage("Timeout of 100 ms exceeded").fail();
-        }
-      }
-    } while (reviewers == null);
-
-    assertThat(reviewers.stream().map(a -> a._accountId).collect(toSet()))
-        .containsExactlyElementsIn(ImmutableSet.of(user.id().get(), user2.id().get()));
+    assertThat(reviewersFor(changeId))
+        .containsExactlyElementsIn(ImmutableSet.of(user.id(), user2.id()));
   }
 
   @Test
   public void addReviewersMatchMultipleSections() throws Exception {
-    RevCommit oldHead = projectOperations.project(project).getHead("master");
     TestAccount user2 = accountCreator.user2();
-
-    Config cfg = new Config();
-    cfg.setStringList(SECTION_FILTER, "*", KEY_REVIEWER, ImmutableList.of(user.email()));
-    cfg.setStringList(SECTION_FILTER, "^a.txt", KEY_REVIEWER, ImmutableList.of(user2.email()));
-
-    pushFactory
-        .create(admin.newIdent(), testRepo, "Add reviewers", FILENAME, cfg.toText())
-        .to(RefNames.REFS_CONFIG)
-        .assertOkStatus();
-
-    testRepo.reset(oldHead);
+    createFilters(filter("*").reviewer(user), filter("\"^a.txt\"").reviewer(user2));
     String changeId = createChange().getChangeId();
-
-    Collection<AccountInfo> reviewers;
-    // Wait for 100 ms until the create patch set event
-    // is processed by the reviewers plugin
-    long wait = 0;
-    do {
-      reviewers = gApi.changes().id(changeId).get().reviewers.get(REVIEWER);
-      if (reviewers == null) {
-        Thread.sleep(10);
-        wait += 10;
-        if (wait > 100) {
-          assertWithMessage("Timeout of 100 ms exceeded").fail();
-        }
-      }
-    } while (reviewers == null);
-
-    assertThat(reviewers.stream().map(a -> a._accountId).collect(toSet()))
-        .containsExactlyElementsIn(ImmutableSet.of(user.id().get(), user2.id().get()));
+    assertThat(reviewersFor(changeId))
+        .containsExactlyElementsIn(ImmutableSet.of(user.id(), user2.id()));
   }
 
   @Test
   public void doNotAddReviewersFromNonMatchingFilters() throws Exception {
-    RevCommit oldHead = projectOperations.project(project).getHead("master");
-
-    Config cfg = new Config();
-    cfg.setString(SECTION_FILTER, "branch:master", KEY_REVIEWER, user.email());
-
-    pushFactory
-        .create(admin.newIdent(), testRepo, "Add reviewers", FILENAME, cfg.toText())
-        .to(RefNames.REFS_CONFIG)
-        .assertOkStatus();
-
-    testRepo.reset(oldHead);
-
+    createFilters(filter("branch:master").reviewer(user));
     createBranch(BranchNameKey.create(project, "other-branch"));
-
     // Create a change that matches the filter section.
     createChange("refs/for/master");
-
     // The actual change we want to test
     String changeId = createChange("refs/for/other-branch").getChangeId();
-
-    Collection<AccountInfo> reviewers;
-    long wait = 0;
-    do {
-      Thread.sleep(10);
-      wait += 10;
-      reviewers = gApi.changes().id(changeId).get().reviewers.get(REVIEWER);
-    } while (reviewers == null && wait < 100);
-
-    assertThat(reviewers).isNull();
+    assertNoReviewersAddedFor(changeId);
   }
 
   @Test
   public void addReviewersFromMatchingFilters() throws Exception {
-    RevCommit oldHead = projectOperations.project(project).getHead("master");
-
-    Config cfg = new Config();
-    cfg.setString(SECTION_FILTER, "branch:other-branch", KEY_REVIEWER, user.email());
-
-    pushFactory
-        .create(admin.newIdent(), testRepo, "Add reviewers", FILENAME, cfg.toText())
-        .to(RefNames.REFS_CONFIG)
-        .assertOkStatus();
-
-    testRepo.reset(oldHead);
-
-    createBranch(BranchNameKey.create(project, "other-branch"));
-
+    createFilters(filter("branch:other-branch").reviewer(user));
     // Create a change that doesn't match the filter section.
     createChange("refs/for/master");
-
     // The actual change we want to test
+    createBranch(BranchNameKey.create(project, "other-branch"));
     String changeId = createChange("refs/for/other-branch").getChangeId();
+    assertThat(reviewersFor(changeId)).containsExactlyElementsIn(ImmutableSet.of(user.id()));
+  }
 
-    Collection<AccountInfo> reviewers;
-    long wait = 0;
-    do {
-      Thread.sleep(10);
-      wait += 10;
-      reviewers = gApi.changes().id(changeId).get().reviewers.get(REVIEWER);
-    } while (reviewers == null && wait < 100);
+  @Test
+  public void dontAddReviewersForPrivateChange() throws Exception {
+    createFilters(filter("*").reviewer(user));
+    PushOneCommit.Result r = createChange("refs/for/master%private");
+    assertThat(r.getChange().change().isPrivate()).isTrue();
+    assertNoReviewersAddedFor(r.getChangeId());
+  }
 
-    assertThat(reviewers.stream().map(a -> a._accountId).collect(toSet()))
-        .containsExactlyElementsIn(ImmutableSet.of(user.id().get()));
+  @Test
+  public void privateBitFlippedAndReviewersAddedOnSubmit() throws Exception {
+    createFilters(filter("*").reviewer(user));
+    PushOneCommit.Result r = createChange("refs/for/master%private");
+    assertThat(r.getChange().change().isPrivate()).isTrue();
+    String changeId = r.getChangeId();
+    assertNoReviewersAddedFor(changeId);
+    // This adds admin as reviewer
+    gApi.changes().id(changeId).current().review(ReviewInput.approve());
+    gApi.changes().id(changeId).current().submit();
+    assertThat(reviewersFor(changeId))
+        .containsExactlyElementsIn(ImmutableSet.of(admin.id(), user.id()));
+    ChangeInfo info = gApi.changes().id(changeId).get();
+    assertThat(info.status).isEqualTo(ChangeStatus.MERGED);
+    assertThat(info.isPrivate).isNull();
+  }
+
+  @Test
+  public void reviewerAddedOnPrivateBitFlip() throws Exception {
+    createFilters(filter("*").reviewer(user));
+    PushOneCommit.Result r = createChange("refs/for/master%private");
+    assertThat(r.getChange().change().isPrivate()).isTrue();
+    String changeId = r.getChangeId();
+    assertNoReviewersAddedFor(changeId);
+    gApi.changes().id(changeId).setPrivate(false);
+    ChangeInfo info = gApi.changes().id(changeId).get();
+    assertThat(info.isPrivate).isNull();
+    assertThat(reviewersFor(changeId)).containsExactlyElementsIn(ImmutableSet.of(user.id()));
+  }
+
+  private Set<Account.Id> reviewersFor(String changeId) throws Exception {
+    return gApi.changes().id(changeId).get().reviewers.get(REVIEWER).stream()
+        .map(a -> Account.id(a._accountId))
+        .collect(toSet());
+  }
+
+  private void assertNoReviewersAddedFor(String changeId) throws Exception {
+    assertThat(gApi.changes().id(changeId).get().reviewers.get(REVIEWER)).isNull();
   }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersResolverIT.java b/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersResolverIT.java
index 38f367d..607c9f5 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersResolverIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersResolverIT.java
@@ -41,7 +41,7 @@
   }
 
   @Test
-  public void testUploaderSkippedAsReviewer() throws Exception {
+  public void uploaderSkippedAsReviewer() throws Exception {
     Set<Account.Id> reviewers =
         resolver.resolve(
             Collections.singleton(user.email()),
@@ -52,7 +52,7 @@
   }
 
   @Test
-  public void testAccountResolve() throws Exception {
+  public void accountResolve() throws Exception {
     Set<Account.Id> reviewers =
         resolver.resolve(
             ImmutableSet.of(user.email(), admin.email()),
@@ -63,7 +63,7 @@
   }
 
   @Test
-  public void testAccountGroupResolve() throws Exception {
+  public void accountGroupResolve() throws Exception {
     String group1 = "group1";
     groupOperations.newGroup().name(group1).create();
     TestAccount foo = createTestAccount("foo", group1);
@@ -87,6 +87,6 @@
 
   private TestAccount createTestAccount(String name, String group) throws Exception {
     name = name(name);
-    return accountCreator.create(name, name + "@example.com", name + " full name", group);
+    return accountCreator.create(name, name + "@example.com", name + " full name", null, group);
   }
 }
diff --git a/tools/BUILD b/tools/BUILD
deleted file mode 100644
index 1fa2160..0000000
--- a/tools/BUILD
+++ /dev/null
@@ -1 +0,0 @@
-# Empty file - bazel treat directories with BUILD file as a package
\ No newline at end of file
diff --git a/tools/bzl/BUILD b/tools/bzl/BUILD
deleted file mode 100644
index c5ed0b7..0000000
--- a/tools/bzl/BUILD
+++ /dev/null
@@ -1 +0,0 @@
-# Empty file required by Bazel
diff --git a/tools/bzl/classpath.bzl b/tools/bzl/classpath.bzl
deleted file mode 100644
index c921d01..0000000
--- a/tools/bzl/classpath.bzl
+++ /dev/null
@@ -1,6 +0,0 @@
-load(
-    "@com_googlesource_gerrit_bazlets//tools:classpath.bzl",
-    _classpath_collector = "classpath_collector",
-)
-
-classpath_collector = _classpath_collector
diff --git a/tools/bzl/genrule2.bzl b/tools/bzl/genrule2.bzl
deleted file mode 100644
index 61c4e18..0000000
--- a/tools/bzl/genrule2.bzl
+++ /dev/null
@@ -1,3 +0,0 @@
-load("@com_googlesource_gerrit_bazlets//tools:genrule2.bzl", _genrule2 = "genrule2")
-
-genrule2 = _genrule2
diff --git a/tools/bzl/js.bzl b/tools/bzl/js.bzl
deleted file mode 100644
index 0f9e367..0000000
--- a/tools/bzl/js.bzl
+++ /dev/null
@@ -1,3 +0,0 @@
-load("@com_googlesource_gerrit_bazlets//tools:js.bzl", _polygerrit_plugin = "polygerrit_plugin")
-
-polygerrit_plugin = _polygerrit_plugin
diff --git a/tools/bzl/junit.bzl b/tools/bzl/junit.bzl
deleted file mode 100644
index 97307bd..0000000
--- a/tools/bzl/junit.bzl
+++ /dev/null
@@ -1,6 +0,0 @@
-load(
-    "@com_googlesource_gerrit_bazlets//tools:junit.bzl",
-    _junit_tests = "junit_tests",
-)
-
-junit_tests = _junit_tests
diff --git a/tools/bzl/plugin.bzl b/tools/bzl/plugin.bzl
deleted file mode 100644
index 4d2dbdd..0000000
--- a/tools/bzl/plugin.bzl
+++ /dev/null
@@ -1,10 +0,0 @@
-load(
-    "@com_googlesource_gerrit_bazlets//:gerrit_plugin.bzl",
-    _gerrit_plugin = "gerrit_plugin",
-    _plugin_deps = "PLUGIN_DEPS",
-    _plugin_test_deps = "PLUGIN_TEST_DEPS",
-)
-
-gerrit_plugin = _gerrit_plugin
-PLUGIN_DEPS = _plugin_deps
-PLUGIN_TEST_DEPS = _plugin_test_deps
diff --git a/tools/eclipse/BUILD b/tools/eclipse/BUILD
deleted file mode 100644
index b51010e..0000000
--- a/tools/eclipse/BUILD
+++ /dev/null
@@ -1,7 +0,0 @@
-load("//tools/bzl:classpath.bzl", "classpath_collector")
-
-classpath_collector(
-    name = "main_classpath_collect",
-    testonly = 1,
-    deps = ["//:reviewers__plugin_test_deps"],
-)
diff --git a/tools/eclipse/project.sh b/tools/eclipse/project.sh
deleted file mode 100755
index 43ae7cc..0000000
--- a/tools/eclipse/project.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/bash
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-`bazel query @com_googlesource_gerrit_bazlets//tools/eclipse:project --output location | sed s/BUILD:.*//`project.py -n reviewers -r .
diff --git a/tools/workspace_status.py b/tools/workspace_status.py
deleted file mode 100644
index 17ccd03..0000000
--- a/tools/workspace_status.py
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/usr/bin/env python
-
-# This script will be run by bazel when the build process starts to
-# generate key-value information that represents the status of the
-# workspace. The output should be like
-#
-# KEY1 VALUE1
-# KEY2 VALUE2
-#
-# If the script exits with non-zero code, it's considered as a failure
-# and the output will be discarded.
-
-from __future__ import print_function
-import subprocess
-import sys
-
-CMD = ['git', 'describe', '--always', '--match', 'v[0-9].*', '--dirty']
-
-
-def revision():
-    try:
-        return subprocess.check_output(CMD).strip().decode("utf-8")
-    except OSError as err:
-        print('could not invoke git: %s' % err, file=sys.stderr)
-        sys.exit(1)
-    except subprocess.CalledProcessError as err:
-        print('error using git: %s' % err, file=sys.stderr)
-        sys.exit(1)
-
-
-print("STABLE_BUILD_REVIEWERS_LABEL %s" % revision())