Merge "Test that creating 2 changes for the same commit on the same branch is not possible"
diff --git a/.bazelproject b/.bazelproject
index b1c8bd3..e14c108 100644
--- a/.bazelproject
+++ b/.bazelproject
@@ -22,6 +22,3 @@
build_flags:
--javacopt=-g
- # Temporarily add an option to work around an error in the Bazel IntelliJ plugin.
- # TODO(aliceks): Remove when issue is fixed.
- --incompatible_depset_is_not_iterable=false
diff --git a/.bazelversion b/.bazelversion
index 1b58cc1..8862dba 100644
--- a/.bazelversion
+++ b/.bazelversion
@@ -1 +1 @@
-0.27.0
+1.0.0rc2
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index b6c034f..d77a21d 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -2840,6 +2840,46 @@
+
Defaults to 300000 ms (5 minutes).
+
+[[index.name.maxMergeCount]]index.name.maxMergeCount::
++
+Determines the max number of simultaneous merges that are allowed. If a merge
+is necessary yet we already have this many threads running, the incoming thread
+(that is calling add/updateDocument) will block until a merge thread has
+completed. Note that Lucene will only run the smallest maxThreadCount merges
+at a time. See the
+link:https://lucene.apache.org/core/5_5_0/core/org/apache/lucene/index/ConcurrentMergeScheduler.html#setDefaultMaxMergesAndThreads(boolean)[
+Lucene documentation] for further details.
++
+Defaults to -1 for (auto detection).
+
+
+[[index.name.maxThreadCount]]index.name.maxThreadCount::
++
+Determines the max number of simultaneous Lucene merge threads that should be running at
+once. This must be less than or equal to maxMergeCount. See the
+link:https://lucene.apache.org/core/5_5_0/core/org/apache/lucene/index/ConcurrentMergeScheduler.html#setDefaultMaxMergesAndThreads(boolean)[
+Lucene documentation] for further details.
++
+For further details on Lucene index configuration (auto detection) which
+affects maxThreadCount and maxMergeCount settings.
+See the
+link:https://lucene.apache.org/core/5_5_0/core/org/apache/lucene/index/ConcurrentMergeScheduler.html#AUTO_DETECT_MERGES_AND_THREADS[
+Lucene documentation]
++
+Defaults to -1 for (auto detection).
+
+[[index.name.enableAutoIOThrottle]]index.name.enableAutoIOThrottle::
++
+Allows the control of whether automatic IO throttling is enabled and used by
+default in the lucene merge queue. Automatic dynamic IO throttling, which when
+on is used to adaptively rate limit writes bytes/sec to the minimal rate necessary
+so merges do not fall behind. See the
+link:https://lucene.apache.org/core/5_5_0/core/org/apache/lucene/index/ConcurrentMergeScheduler.html#enableAutoIOThrottle()[
+Lucene documentation] for further details.
++
+Defaults to true (throttling enabled).
+
Sample Lucene index configuration:
----
[index]
@@ -2848,10 +2888,17 @@
[index "changes_open"]
ramBufferSize = 60 m
maxBufferedDocs = 3000
+ maxThreadCount = 5
+ maxMergeCount = 50
+
[index "changes_closed"]
ramBufferSize = 20 m
maxBufferedDocs = 500
+ maxThreadCount = 10
+ maxMergeCount = 100
+ enableIOThrottle = false
+
----
[[elasticsearch]]
diff --git a/Documentation/dev-crafting-changes.txt b/Documentation/dev-crafting-changes.txt
index 76baa8b..bf4453c 100644
--- a/Documentation/dev-crafting-changes.txt
+++ b/Documentation/dev-crafting-changes.txt
@@ -115,7 +115,7 @@
link:https://github.com/google/google-java-format[`google-java-format`]
tool (version 1.7), and to format Bazel BUILD, WORKSPACE and .bzl files the
link:https://github.com/bazelbuild/buildtools/tree/master/buildifier[`buildifier`]
-tool (version 0.26.0).
+tool (version 0.29.0).
These tools automatically apply format according to the style guides; this
streamlines code review by reducing the need for time-consuming, tedious,
and contentious discussions about trivial issues like whitespace.
diff --git a/Documentation/dev-e2e-tests.txt b/Documentation/dev-e2e-tests.txt
new file mode 100644
index 0000000..7329a43
--- /dev/null
+++ b/Documentation/dev-e2e-tests.txt
@@ -0,0 +1,92 @@
+= Gerrit Code Review - End to end load tests
+
+This document provides a description of a Gerrit load test scenario implemented using the link:http://gatling.io[`Gatling`] framework.
+
+Similar scenarios have been successfully used to compare performance of different Gerrit versions or study the Gerrit response
+under different load profiles.
+
+== What is Gatling?
+
+Gatling is a load testing tool which provides out of the box support for the HTTP protocol. Documentation on how to write an
+HTTP load test can be found link:https://gatling.io/docs/current/http/http_protocol/[`here`].
+
+However, in the scenario we are proposing, we are leveraging the link:https://github.com/GerritForge/gatling-git[`Gatling Git extension`]
+to run tests at Git protocol level.
+
+Gatling is written in Scala, but the abstraction provided by the Gatling DSL makes the scenarios implementation easy even without any Scala knowledge.
+
+Examples of scenarios can be found in the `e2e-tests` directory.
+
+=== How to run the load tests
+
+==== Prerequisites
+
+* link:https://www.scala-lang.org/download/[`Scala 2.12`]
+
+==== How to build
+
+----
+sbt compile
+----
+
+==== Setup
+
+If you are running SSH commands the private keys of the users used for testing need to go in `/tmp/ssh-keys`.
+The keys need to be generated this way (JSch won't validate them [otherwise](https://stackoverflow.com/questions/53134212/invalid-privatekey-when-using-jsch):
+
+----
+ssh-keygen -m PEM -t rsa -C "test@mail.com" -f /tmp/ssh-keys/id_rsa
+----
+
+*NOTE*: Don't forget to add the public keys for the testing user(s) to your git server
+
+==== Input file
+
+The ReplayRecordsScenario is fed by the data coming from the [src/test/resources/data/requests.json](/src/test/resources/data/requests.json) file.
+Such file contains the commands and repo used during the load test.
+Below an example:
+
+----
+[
+ {
+ "url": "ssh://admin@localhost:29418/loadtest-repo.git",
+ "cmd": "clone"
+ },
+ {
+ "url": "http://localhost:8080/loadtest-repo.git",
+ "cmd": "fetch"
+ }
+]
+----
+
+Valid commands are:
+* fetch
+* pull
+* push
+* clone
+
+==== How to use the framework
+
+Run all tests:
+----
+sbt "gatling:test"
+----
+
+Run a single test:
+----
+sbt "gatling:testOnly com.google.gerrit.scenarios.ReplayRecordsFromFeederScenario"
+----
+
+Generate the last report:
+----
+sbt "gatling:lastReport"
+----
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
+
+[scala]:
diff --git a/Documentation/pg-plugin-admin-api.txt b/Documentation/pg-plugin-admin-api.txt
index 1c724d0..084fa2c 100644
--- a/Documentation/pg-plugin-admin-api.txt
+++ b/Documentation/pg-plugin-admin-api.txt
@@ -4,7 +4,7 @@
and provides customization of the admin menu.
== addMenuLink
-`adminApi.addMenuLink(text, url, opt_external)`
+`adminApi.addMenuLink(text, url, opt_external, opt_capabilities)`
Add a new link to the end of the admin navigation menu.
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 145af0e..5bbc9b8 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -57,8 +57,8 @@
[[details]]
--
-* `DETAILS`: Includes full name, preferred email, username and avatars
-for each account.
+* `DETAILS`: Includes full name, preferred email, username, avatars and
+status for each account.
--
[[all-emails]]
@@ -2260,9 +2260,12 @@
See option link:rest-api-changes.html#detailed-accounts[
DETAILED_ACCOUNTS] for change queries +
and option link:#details[DETAILS] for account queries.
+|`avatars` |optional|List of link:#avatar-info[AvatarInfo] +
+entities that provide information about avatar images of the account.
|`_more_accounts` |optional, not set if `false`|
Whether the query would deliver more results if not limited. +
Only set on the last account that is returned.
+|`status` |optional|Status message of the account.
|===============================
[[account-input]]
@@ -2308,6 +2311,19 @@
If not set or if set to an empty string, the account status is deleted.
|=============================
+[[avatar-info]]
+=== AvatarInfo
+The `AccountInfo` entity contains information about an avatar image of
+an account.
+
+[options="header",cols="1,6"]
+|======================
+|Field Name|Description
+|`url` |The URL to the avatar image.
+|`height` |The height of the avatar image in pixels.
+|`width` |The width of the avatar image in pixels.
+|======================
+
[[capability-info]]
=== CapabilityInfo
The `CapabilityInfo` entity contains information about the global
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index f958f44..d2f4d97 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -3088,6 +3088,12 @@
The reviewer to be added to the change must be provided in the request
body as a link:#reviewer-input[ReviewerInput] entity.
+Users can be moved from reviewer to CC and vice versa. This means if a
+user is added as CC that is already a reviewer on the change, the
+reviewer state of that user is updated to CC. If a user that is already
+a CC on the change is added as reviewer, the reviewer state of that
+user is updated to reviewer.
+
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/reviewers HTTP/1.0
@@ -3131,6 +3137,12 @@
If a group with many members is added as reviewer a confirmation may be
required.
+If a group is added as CC and members of this group are already
+reviewers on the change, these members stay reviewers on the change
+(they are not downgraded to CC). However if a group is added as
+reviewer, all group members become reviewer of the change, even if they
+have been added as CC before.
+
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/reviewers HTTP/1.0
diff --git a/WORKSPACE b/WORKSPACE
index 284ed48..83ee604 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -64,9 +64,26 @@
urls = ["https://raw.githubusercontent.com/google/closure-compiler/35d2b3340ff23a69441f10fa3bc820691c2942f2/contrib/externs/polymer-1.0.js"],
)
-load("@bazel_skylib//lib:versions.bzl", "versions")
+# Check Bazel version when invoked by Bazel directly
+load("//tools/bzl:bazelisk_version.bzl", "bazelisk_version")
-versions.check(minimum_bazel_version = "0.26.1")
+bazelisk_version(name = "bazelisk_version")
+
+load("@bazelisk_version//:check.bzl", "check_bazel_version")
+
+check_bazel_version()
+
+# Rules Python
+http_archive(
+ name = "rules_python",
+ sha256 = "b5bab4c47e863e0fbb77df4a40c45ca85f98f5a2826939181585644c9f31b97b",
+ strip_prefix = "rules_python-9d68f24659e8ce8b736590ba1e4418af06ec2552",
+ urls = ["https://github.com/bazelbuild/rules_python/archive/9d68f24659e8ce8b736590ba1e4418af06ec2552.tar.gz"],
+)
+
+load("@rules_python//python:repositories.bzl", "py_repositories")
+
+py_repositories()
load("@io_bazel_rules_closure//closure:defs.bzl", "closure_repositories")
@@ -106,6 +123,17 @@
gazelle_dependencies()
+# Protobuf rules support
+http_archive(
+ name = "rules_proto",
+ sha256 = "602e7161d9195e50246177e7c55b2f39950a9cf7366f74ed5f22fd45750cd208",
+ strip_prefix = "rules_proto-97d8af4dc474595af3900dd85cb3a29ad28cc313",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/rules_proto/archive/97d8af4dc474595af3900dd85cb3a29ad28cc313.tar.gz",
+ "https://github.com/bazelbuild/rules_proto/archive/97d8af4dc474595af3900dd85cb3a29ad28cc313.tar.gz",
+ ],
+)
+
# Dependencies for PolyGerrit local dev server.
go_repository(
name = "com_github_howeyc_fsnotify",
@@ -723,7 +751,7 @@
sha1 = "f7be08ec23c21485b9b5a1cf1654c2ec8c58168d",
)
-GITILES_VERS = "0.3-3"
+GITILES_VERS = "0.3-4"
GITILES_REPO = GERRIT
@@ -732,14 +760,14 @@
artifact = "com.google.gitiles:blame-cache:" + GITILES_VERS,
attach_source = False,
repository = GITILES_REPO,
- sha1 = "d0bcfc6292b8d69a76156baa620ac892f68a5d23",
+ sha1 = "d1d62c9905b0cc9e066d337b33480599f430eb87",
)
maven_jar(
name = "gitiles-servlet",
artifact = "com.google.gitiles:gitiles-servlet:" + GITILES_VERS,
repository = GITILES_REPO,
- sha1 = "7c010f892c346fe3278df15f73b1588a110f0187",
+ sha1 = "7360cb90576a813cd1288cf853c59824fe92467e",
)
# prettify must match the version used in Gitiles
@@ -752,8 +780,8 @@
# Keep this version of Soy synchronized with the version used in Gitiles.
maven_jar(
name = "soy",
- artifact = "com.google.template:soy:2019-08-22",
- sha1 = "d4bf390caf7aa448108a5b9ec1b51f46820438f3",
+ artifact = "com.google.template:soy:2019-09-03",
+ sha1 = "40781da0302b4b5d53006dc8bd5a432c7288d807",
)
maven_jar(
@@ -1072,18 +1100,18 @@
sha1 = "0f5a654e4675769c716e5b387830d19b501ca191",
)
-TESTCONTAINERS_VERSION = "1.12.0"
+TESTCONTAINERS_VERSION = "1.12.1"
maven_jar(
name = "testcontainers",
artifact = "org.testcontainers:testcontainers:" + TESTCONTAINERS_VERSION,
- sha1 = "ac89643ce1ddde504da09172086aba0c7df10bff",
+ sha1 = "1dc8666ead914c5515d087f75ffe92629414caf6",
)
maven_jar(
name = "testcontainers-elasticsearch",
artifact = "org.testcontainers:elasticsearch:" + TESTCONTAINERS_VERSION,
- sha1 = "cd9020f1803396c45ef935312bf232f9b17332b0",
+ sha1 = "2491f792627a1f15d341bfcd6dd0ea7e3541d82f",
)
maven_jar(
@@ -1119,13 +1147,13 @@
BYTE_BUDDY_VERSION = "1.9.7"
maven_jar(
- name = "byte-buddy",
+ name = "bytebuddy",
artifact = "net.bytebuddy:byte-buddy:" + BYTE_BUDDY_VERSION,
sha1 = "8fea78fea6449e1738b675cb155ce8422661e237",
)
maven_jar(
- name = "byte-buddy-agent",
+ name = "bytebuddy-agent",
artifact = "net.bytebuddy:byte-buddy-agent:" + BYTE_BUDDY_VERSION,
sha1 = "8e7d1b599f4943851ffea125fd9780e572727fc0",
)
diff --git a/antlr3/BUILD b/antlr3/BUILD
index 2d3050e..549946a 100644
--- a/antlr3/BUILD
+++ b/antlr3/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//tools/bzl:genrule2.bzl", "genrule2")
genrule2(
diff --git a/contrib/remove-notedb-refs.sh b/contrib/remove-notedb-refs.sh
new file mode 100755
index 0000000..3c13067
--- /dev/null
+++ b/contrib/remove-notedb-refs.sh
@@ -0,0 +1,56 @@
+# 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.
+#!/usr/bin/env bash
+
+set -e
+
+if [[ "$#" -lt "2" ]] ; then
+ cat <<EOF
+Usage: run "$0 /path/to/git/dir [project...]" or "$0 /path/to/git/dir ALL"
+
+This util script can be used in case of rollback to ReviewDB during an unsuccessful
+migration to NoteDB or simply while testing the migration process.
+
+It will remove all the refs used by NoteDB added during the migration (i.e.: change meta refs and sequence ref).
+EOF
+ exit 1
+fi
+
+GERRIT_GIT_DIR=$1
+shift
+
+ALL_PROJECTS=$@
+if [[ "$2" -eq "ALL" ]] ; then
+ ALL_PROJECTS=`find "${GERRIT_GIT_DIR}" -type d -name "*.git"`
+fi
+
+ALL_PROJECTS_ARRAY=(${ALL_PROJECTS// / })
+
+for project in "${ALL_PROJECTS_ARRAY[@]}"
+do
+ if [[ "$project" =~ /All-Users\.git$ ]]; then
+ echo "Skipping $project ..."
+ else
+ echo "Removing meta ref for $project ..."
+ cd "$project"
+ if `git show-ref meta | grep -q "/meta$"`; then
+ git show-ref meta | grep "/meta$" | cut -d' ' -f2 | xargs -L1 git update-ref -d
+ fi
+ fi
+done
+
+echo "Remove sequence ref"
+allProjectDir="$GERRIT_GIT_DIR/All-Projects.git"
+cd $allProjectDir
+git update-ref -d refs/sequences/changes
diff --git a/e2e-tests/load-tests/.gitignore b/e2e-tests/load-tests/.gitignore
new file mode 100644
index 0000000..052f424
--- /dev/null
+++ b/e2e-tests/load-tests/.gitignore
@@ -0,0 +1,16 @@
+.idea/
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+### Scala ###
+*.class
+*.log
+target
+project/target
diff --git a/e2e-tests/load-tests/build.sbt b/e2e-tests/load-tests/build.sbt
new file mode 100644
index 0000000..46a3202
--- /dev/null
+++ b/e2e-tests/load-tests/build.sbt
@@ -0,0 +1,18 @@
+import Dependencies._
+
+enablePlugins(GatlingPlugin)
+
+lazy val gatlingGitExtension = RootProject(uri("git://github.com/GerritForge/gatling-git.git"))
+lazy val root = (project in file("."))
+ .settings(
+ inThisBuild(List(
+ organization := "com.google.gerrit",
+ scalaVersion := "2.12.8",
+ version := "0.1.0-SNAPSHOT"
+ )),
+ name := "gerrit",
+ libraryDependencies ++=
+ gatling ++
+ Seq("io.gatling" % "gatling-core" % "3.1.1" ) ++
+ Seq("io.gatling" % "gatling-app" % "3.1.1" )
+ ) dependsOn(gatlingGitExtension)
diff --git a/e2e-tests/load-tests/project/Dependencies.scala b/e2e-tests/load-tests/project/Dependencies.scala
new file mode 100644
index 0000000..72d2ac2
--- /dev/null
+++ b/e2e-tests/load-tests/project/Dependencies.scala
@@ -0,0 +1,8 @@
+import sbt._
+
+object Dependencies {
+ lazy val gatling = Seq(
+ "io.gatling.highcharts" % "gatling-charts-highcharts",
+ "io.gatling" % "gatling-test-framework",
+ ).map(_ % "3.1.1" % Test)
+}
diff --git a/e2e-tests/load-tests/project/build.properties b/e2e-tests/load-tests/project/build.properties
new file mode 100644
index 0000000..0cd8b07
--- /dev/null
+++ b/e2e-tests/load-tests/project/build.properties
@@ -0,0 +1 @@
+sbt.version=1.2.3
diff --git a/e2e-tests/load-tests/project/plugins.sbt b/e2e-tests/load-tests/project/plugins.sbt
new file mode 100644
index 0000000..36cd201
--- /dev/null
+++ b/e2e-tests/load-tests/project/plugins.sbt
@@ -0,0 +1 @@
+addSbtPlugin("io.gatling" % "gatling-sbt" % "3.0.0")
diff --git a/e2e-tests/load-tests/src/test/resources/application.conf b/e2e-tests/load-tests/src/test/resources/application.conf
new file mode 100644
index 0000000..33da75d
--- /dev/null
+++ b/e2e-tests/load-tests/src/test/resources/application.conf
@@ -0,0 +1,30 @@
+http {
+ username: "default_username",
+ username: ${?GIT_HTTP_USERNAME},
+
+ password: "default_password",
+ password: ${?GIT_HTTP_PASSWORD},
+}
+
+ssh {
+ private_key_path: "/tmp/ssh-keys/id_rsa",
+ private_key_path: ${?GIT_SSH_PRIVATE_KEY_PATH},
+}
+
+tmpFiles {
+ basePath: "/tmp"
+ basePath: ${?TMP_BASE_PATH}
+}
+
+commands {
+ push {
+ numFiles: 4
+ numFiles: ${?NUM_FILES}
+ minContentLength: 100
+ minContentLength: ${?MIN_CONTENT_LEGTH}
+ maxContentLength: 10000
+ maxContentLength: ${?MAX_CONTENT_LEGTH}
+ commitPrefix: ""
+ commitPrefix: ${?COMMIT_PREFIX}
+ }
+}
diff --git a/e2e-tests/load-tests/src/test/resources/data/requests.json b/e2e-tests/load-tests/src/test/resources/data/requests.json
new file mode 100644
index 0000000..86f9bf1
--- /dev/null
+++ b/e2e-tests/load-tests/src/test/resources/data/requests.json
@@ -0,0 +1,26 @@
+[
+ {
+ "url": "ssh://admin@localhost:29418/loadtest-repo",
+ "cmd": "clone"
+ },
+ {
+ "url": "ssh://admin@localhost:29418/loadtest-repo",
+ "cmd": "pull"
+ },
+ {
+ "url": "ssh://admin@localhost:29418/loadtest-repo",
+ "cmd": "push"
+ },
+ {
+ "url": "http://localhost:8080/loadtest-repo",
+ "cmd": "clone"
+ },
+ {
+ "url": "http://localhost:8080/loadtest-repo",
+ "cmd": "pull"
+ },
+ {
+ "url": "http://localhost:8080/loadtest-repo",
+ "cmd": "push"
+ }
+]
diff --git a/e2e-tests/load-tests/src/test/resources/gatling.conf b/e2e-tests/load-tests/src/test/resources/gatling.conf
new file mode 100644
index 0000000..94c371b
--- /dev/null
+++ b/e2e-tests/load-tests/src/test/resources/gatling.conf
@@ -0,0 +1,128 @@
+#########################
+# Gatling Configuration #
+#########################
+
+# This file contains all the settings configurable for Gatling with their default values
+
+gatling {
+ core {
+ #outputDirectoryBaseName = "" # The prefix for each simulation result folder (then suffixed by the report generation timestamp)
+ #runDescription = "" # The description for this simulation run, displayed in each report
+ #encoding = "utf-8" # Encoding to use throughout Gatling for file and string manipulation
+ #simulationClass = "" # The FQCN of the simulation to run (when used in conjunction with noReports, the simulation for which assertions will be validated)
+ #elFileBodiesCacheMaxCapacity = 200 # Cache size for request body EL templates, set to 0 to disable
+ #rawFileBodiesCacheMaxCapacity = 200 # Cache size for request body Raw templates, set to 0 to disable
+ #rawFileBodiesInMemoryMaxSize = 1000 # Below this limit, raw file bodies will be cached in memory
+ #pebbleFileBodiesCacheMaxCapacity = 200 # Cache size for request body Peeble templates, set to 0 to disable
+ #shutdownTimeout = 5000 # Milliseconds to wait for the actor system to shutdown
+ extract {
+ regex {
+ #cacheMaxCapacity = 200 # Cache size for the compiled regexes, set to 0 to disable caching
+ }
+ xpath {
+ #cacheMaxCapacity = 200 # Cache size for the compiled XPath queries, set to 0 to disable caching
+ }
+ jsonPath {
+ #cacheMaxCapacity = 200 # Cache size for the compiled jsonPath queries, set to 0 to disable caching
+ #preferJackson = false # When set to true, prefer Jackson over Boon for JSON-related operations
+ }
+ css {
+ #cacheMaxCapacity = 200 # Cache size for the compiled CSS selectors queries, set to 0 to disable caching
+ }
+ }
+ directory {
+ simulations = "./src/test/scala"
+ #simulations = user-files/simulations # Directory where simulation classes are located (for bundle packaging only)
+ resources = "./src/test/resources/data" # Directory where resources, such as feeder files and request bodies are located (for bundle packaging only)
+ #reportsOnly = "" # If set, name of report folder to look for in order to generate its report
+ binaries = "./target/scala-2.12/classes" # If set, name of the folder where compiles classes are located: Defaults to GATLING_HOME/target.
+ #results = results # Name of the folder where all reports folder are located
+ }
+ }
+ charting {
+ #noReports = false # When set to true, don't generate HTML reports
+ #maxPlotPerSeries = 1000 # Number of points per graph in Gatling reports
+ #useGroupDurationMetric = false # Switch group timings from cumulated response time to group duration.
+ indicators {
+ #lowerBound = 800 # Lower bound for the requests' response time to track in the reports and the console summary
+ #higherBound = 1200 # Higher bound for the requests' response time to track in the reports and the console summary
+ #percentile1 = 50 # Value for the 1st percentile to track in the reports, the console summary and Graphite
+ #percentile2 = 75 # Value for the 2nd percentile to track in the reports, the console summary and Graphite
+ #percentile3 = 95 # Value for the 3rd percentile to track in the reports, the console summary and Graphite
+ #percentile4 = 99 # Value for the 4th percentile to track in the reports, the console summary and Graphite
+ }
+ }
+ http {
+ #fetchedCssCacheMaxCapacity = 200 # Cache size for CSS parsed content, set to 0 to disable
+ #fetchedHtmlCacheMaxCapacity = 200 # Cache size for HTML parsed content, set to 0 to disable
+ #perUserCacheMaxCapacity = 200 # Per virtual user cache size, set to 0 to disable
+ #warmUpUrl = "https://gatling.io" # The URL to use to warm-up the HTTP stack (blank means disabled)
+ #enableGA = true # Very light Google Analytics, please support
+ ssl {
+ keyStore {
+ #type = "" # Type of SSLContext's KeyManagers store
+ #file = "" # Location of SSLContext's KeyManagers store
+ #password = "" # Password for SSLContext's KeyManagers store
+ #algorithm = "" # Algorithm used SSLContext's KeyManagers store
+ }
+ trustStore {
+ #type = "" # Type of SSLContext's TrustManagers store
+ #file = "" # Location of SSLContext's TrustManagers store
+ #password = "" # Password for SSLContext's TrustManagers store
+ #algorithm = "" # Algorithm used by SSLContext's TrustManagers store
+ }
+ }
+ ahc {
+ #connectTimeout = 10000 # Timeout in millis for establishing a TCP socket
+ #handshakeTimeout = 10000 # Timeout in millis for performing TLS handshake
+ #pooledConnectionIdleTimeout = 60000 # Timeout in millis for a connection to stay idle in the pool
+ #maxRetry = 2 # Number of times that a request should be tried again
+ #requestTimeout = 60000 # Timeout in millis for performing an HTTP request
+ #enableSni = true # When set to true, enable Server Name indication (SNI)
+ #enableHostnameVerification = false # When set to true, enable hostname verification: SSLEngine.setHttpsEndpointIdentificationAlgorithm("HTTPS")
+ #useInsecureTrustManager = true # Use an insecure TrustManager that trusts all server certificates
+ #filterInsecureCipherSuites = true # Turn to false to not filter out insecure and weak cipher suites
+ #sslEnabledProtocols = [TLSv1.2, TLSv1.1, TLSv1] # Array of enabled protocols for HTTPS, if empty use the JDK defaults
+ #sslEnabledCipherSuites = [] # Array of enabled cipher suites for HTTPS, if empty use the AHC defaults
+ #sslSessionCacheSize = 0 # SSLSession cache size, set to 0 to use JDK's default
+ #sslSessionTimeout = 0 # SSLSession timeout in seconds, set to 0 to use JDK's default (24h)
+ #disableSslSessionResumption = false # if true, SSLSessions won't be resumed
+ #useOpenSsl = true # if OpenSSL should be used instead of JSSE
+ #useNativeTransport = false # if native transport should be used instead of Java NIO (requires netty-transport-native-epoll, currently Linux only)
+ #enableZeroCopy = true # if zero-copy upload should be used if possible
+ #tcpNoDelay = true
+ #soReuseAddress = false
+ #allocator = "pooled" # switch to unpooled for unpooled ByteBufAllocator
+ #maxThreadLocalCharBufferSize = 200000 # Netty's default is 16k
+ }
+ dns {
+ #queryTimeout = 5000 # Timeout in millis of each DNS query in millis
+ #maxQueriesPerResolve = 6 # Maximum allowed number of DNS queries for a given name resolution
+ }
+ }
+ jms {
+ #replyTimeoutScanPeriod = 1000 # scan period for timedout reply messages
+ }
+ data {
+ #writers = [console, file] # The list of DataWriters to which Gatling write simulation data (currently supported : console, file, graphite, jdbc)
+ console {
+ #light = false # When set to true, displays a light version without detailed request stats
+ #writePeriod = 5 # Write interval, in seconds
+ }
+ file {
+ #bufferSize = 8192 # FileDataWriter's internal data buffer size, in bytes
+ }
+ leak {
+ #noActivityTimeout = 30 # Period, in seconds, for which Gatling may have no activity before considering a leak may be happening
+ }
+ graphite {
+ #light = false # only send the all* stats
+ #host = "localhost" # The host where the Carbon server is located
+ #port = 2003 # The port to which the Carbon server listens to (2003 is default for plaintext, 2004 is default for pickle)
+ #protocol = "tcp" # The protocol used to send data to Carbon (currently supported : "tcp", "udp")
+ #rootPathPrefix = "gatling" # The common prefix of all metrics sent to Graphite
+ #bufferSize = 8192 # Internal data buffer size, in bytes
+ #writePeriod = 1 # Write period, in seconds
+ }
+ }
+}
diff --git a/e2e-tests/load-tests/src/test/resources/hooks/commit-msg b/e2e-tests/load-tests/src/test/resources/hooks/commit-msg
new file mode 100644
index 0000000..b05a671
--- /dev/null
+++ b/e2e-tests/load-tests/src/test/resources/hooks/commit-msg
@@ -0,0 +1,43 @@
+#!/bin/sh
+#
+# Part of Gerrit Code Review (https://www.gerritcodereview.com/)
+#
+# Copyright (C) 2009 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.
+
+# avoid [[ which is not POSIX sh.
+if test "$#" != 1 ; then
+ echo "$0 requires an argument."
+ exit 1
+fi
+
+if test ! -f "$1" ; then
+ echo "file does not exist: $1"
+ exit 1
+fi
+
+if test ! -s "$1" ; then
+ echo "file is empty: $1"
+ exit 1
+fi
+
+# $RANDOM will be undefined if not using bash, so don't use set -u
+random=$( (whoami ; hostname ; date; cat $1 ; echo $RANDOM) | git hash-object --stdin)
+dest="$1.tmp.${random}"
+
+# Avoid the --in-place option which only appeared in Git 2.8
+# Avoid the --if-exists option which only appeared in Git 2.15
+cat "$1" \
+| git -c trailer.ifexists=doNothing interpret-trailers --trailer "Change-Id: I${random}" > "${dest}" \
+&& mv "${dest}" "$1"
diff --git a/e2e-tests/load-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala b/e2e-tests/load-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala
new file mode 100644
index 0000000..c0eab39
--- /dev/null
+++ b/e2e-tests/load-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala
@@ -0,0 +1,72 @@
+// 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.
+
+package com.google.gerrit.scenarios
+
+import com.github.barbasa.gatling.git.protocol.GitProtocol
+import com.github.barbasa.gatling.git.request.builder.GitRequestBuilder
+import io.gatling.core.Predef._
+import io.gatling.core.structure.ScenarioBuilder
+import java.io._
+
+import com.github.barbasa.gatling.git.{
+ GatlingGitConfiguration,
+ GitRequestSession
+}
+import org.apache.commons.io.FileUtils
+
+import scala.concurrent.duration._
+import org.eclipse.jgit.hooks._
+
+class ReplayRecordsFromFeederScenario extends Simulation {
+
+ val gitProtocol = GitProtocol()
+ implicit val conf = GatlingGitConfiguration()
+ implicit val postMessageHook: Option[String] = Some(
+ s"hooks/${CommitMsgHook.NAME}")
+
+ val feeder = jsonFile("data/requests.json").circular
+
+ val replayCallsScenario: ScenarioBuilder =
+ scenario("Git commands")
+ .repeat(10000) {
+ feed(feeder)
+ .exec(new GitRequestBuilder(GitRequestSession("${cmd}", "${url}")))
+ }
+
+ setUp(
+ replayCallsScenario.inject(
+ nothingFor(4 seconds),
+ atOnceUsers(10),
+ rampUsers(10) during (5 seconds),
+ constantUsersPerSec(20) during (15 seconds),
+ constantUsersPerSec(20) during (15 seconds) randomized
+ ))
+ .protocols(gitProtocol)
+ .maxDuration(60 seconds)
+
+ after {
+ try {
+ //After is often called too early. Some retries should be implemented.
+ Thread.sleep(5000)
+ FileUtils.deleteDirectory(new File(conf.tmpBasePath))
+ } catch {
+ case e: IOException => {
+ System.err.println(
+ "Unable to delete temporary directory: " + conf.tmpBasePath)
+ e.printStackTrace
+ }
+ }
+ }
+}
diff --git a/java/BUILD b/java/BUILD
index 4fc4d79..77611e4 100644
--- a/java/BUILD
+++ b/java/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_binary", "java_library")
+
java_binary(
name = "gerrit-main-class",
main_class = "Main",
diff --git a/java/com/google/gerrit/acceptance/BUILD b/java/com/google/gerrit/acceptance/BUILD
index 768c340..eca7ea6 100644
--- a/java/com/google/gerrit/acceptance/BUILD
+++ b/java/com/google/gerrit/acceptance/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_binary", "java_library")
load("//tools/bzl:java.bzl", "java_library2")
load("//tools/bzl:javadoc.bzl", "java_doc")
diff --git a/java/com/google/gerrit/acceptance/GerritServer.java b/java/com/google/gerrit/acceptance/GerritServer.java
index a48a278..ea63d73 100644
--- a/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/java/com/google/gerrit/acceptance/GerritServer.java
@@ -259,6 +259,8 @@
// Silence non-critical messages from JGit.
.put("org.eclipse.jgit.transport.PacketLineIn", Level.WARN)
.put("org.eclipse.jgit.transport.PacketLineOut", Level.WARN)
+ .put("org.eclipse.jgit.internal.storage.file.FileSnapshot", Level.WARN)
+ .put("org.eclipse.jgit.util.FS", Level.WARN)
.build();
private static boolean forceLocalDisk() {
diff --git a/java/com/google/gerrit/acceptance/testsuite/project/BUILD b/java/com/google/gerrit/acceptance/testsuite/project/BUILD
index 3215a9c..1ef2931 100644
--- a/java/com/google/gerrit/acceptance/testsuite/project/BUILD
+++ b/java/com/google/gerrit/acceptance/testsuite/project/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
package(default_testonly = 1)
java_library(
diff --git a/java/com/google/gerrit/asciidoctor/BUILD b/java/com/google/gerrit/asciidoctor/BUILD
index f5178a0..94ec20d 100644
--- a/java/com/google/gerrit/asciidoctor/BUILD
+++ b/java/com/google/gerrit/asciidoctor/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_binary", "java_library")
+
java_binary(
name = "asciidoc",
main_class = "com.google.gerrit.asciidoctor.AsciiDoctor",
diff --git a/java/com/google/gerrit/common/BUILD b/java/com/google/gerrit/common/BUILD
index b35b8bf..113ff53 100644
--- a/java/com/google/gerrit/common/BUILD
+++ b/java/com/google/gerrit/common/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
ANNOTATIONS = [
"Nullable.java",
"UsedAt.java",
diff --git a/java/com/google/gerrit/common/data/testing/BUILD b/java/com/google/gerrit/common/data/testing/BUILD
index 32815d5..8ab01de 100644
--- a/java/com/google/gerrit/common/data/testing/BUILD
+++ b/java/com/google/gerrit/common/data/testing/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "common-data-test-util",
testonly = True,
diff --git a/java/com/google/gerrit/elasticsearch/BUILD b/java/com/google/gerrit/elasticsearch/BUILD
index f919aad..a9b145b 100644
--- a/java/com/google/gerrit/elasticsearch/BUILD
+++ b/java/com/google/gerrit/elasticsearch/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "elasticsearch",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/exceptions/BUILD b/java/com/google/gerrit/exceptions/BUILD
index 50bf883..e08c3fd 100644
--- a/java/com/google/gerrit/exceptions/BUILD
+++ b/java/com/google/gerrit/exceptions/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "exceptions",
srcs = glob(["*.java"]),
diff --git a/java/com/google/gerrit/extensions/BUILD b/java/com/google/gerrit/extensions/BUILD
index b69d2c8..0022584 100644
--- a/java/com/google/gerrit/extensions/BUILD
+++ b/java/com/google/gerrit/extensions/BUILD
@@ -1,5 +1,6 @@
-load("//lib/jgit:jgit.bzl", "JGIT_DOC_URL")
+load("@rules_java//java:defs.bzl", "java_binary", "java_library")
load("//lib:guava.bzl", "GUAVA_DOC_URL")
+load("//lib/jgit:jgit.bzl", "JGIT_DOC_URL")
load("//tools/bzl:javadoc.bzl", "java_doc")
java_binary(
diff --git a/java/com/google/gerrit/extensions/common/testing/BUILD b/java/com/google/gerrit/extensions/common/testing/BUILD
index 7092d21..9cecb66 100644
--- a/java/com/google/gerrit/extensions/common/testing/BUILD
+++ b/java/com/google/gerrit/extensions/common/testing/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "common-test-util",
testonly = True,
diff --git a/java/com/google/gerrit/extensions/restapi/testing/BUILD b/java/com/google/gerrit/extensions/restapi/testing/BUILD
index 3417cae2..4c44d2a 100644
--- a/java/com/google/gerrit/extensions/restapi/testing/BUILD
+++ b/java/com/google/gerrit/extensions/restapi/testing/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "restapi-test-util",
testonly = True,
diff --git a/java/com/google/gerrit/git/BUILD b/java/com/google/gerrit/git/BUILD
index fc146dc..5ece37a 100644
--- a/java/com/google/gerrit/git/BUILD
+++ b/java/com/google/gerrit/git/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "git",
srcs = glob(["*.java"]),
diff --git a/java/com/google/gerrit/git/testing/BUILD b/java/com/google/gerrit/git/testing/BUILD
index 497510d..13fddc1 100644
--- a/java/com/google/gerrit/git/testing/BUILD
+++ b/java/com/google/gerrit/git/testing/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
package(default_testonly = True)
java_library(
diff --git a/java/com/google/gerrit/gpg/BUILD b/java/com/google/gerrit/gpg/BUILD
index 49806cf..d45a86d 100644
--- a/java/com/google/gerrit/gpg/BUILD
+++ b/java/com/google/gerrit/gpg/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "gpg",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/gpg/testing/BUILD b/java/com/google/gerrit/gpg/testing/BUILD
index 0282d3a..b227dd5 100644
--- a/java/com/google/gerrit/gpg/testing/BUILD
+++ b/java/com/google/gerrit/gpg/testing/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "gpg-test-util",
testonly = True,
diff --git a/java/com/google/gerrit/httpd/BUILD b/java/com/google/gerrit/httpd/BUILD
index f86b35d5..c4b0314 100644
--- a/java/com/google/gerrit/httpd/BUILD
+++ b/java/com/google/gerrit/httpd/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "httpd",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/httpd/auth/oauth/BUILD b/java/com/google/gerrit/httpd/auth/oauth/BUILD
index dd3e5fc..b74a65a 100644
--- a/java/com/google/gerrit/httpd/auth/oauth/BUILD
+++ b/java/com/google/gerrit/httpd/auth/oauth/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "oauth",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/httpd/auth/openid/BUILD b/java/com/google/gerrit/httpd/auth/openid/BUILD
index edd12cc..ad9d3aa 100644
--- a/java/com/google/gerrit/httpd/auth/openid/BUILD
+++ b/java/com/google/gerrit/httpd/auth/openid/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "openid",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/httpd/init/BUILD b/java/com/google/gerrit/httpd/init/BUILD
index df072b2..09772fd 100644
--- a/java/com/google/gerrit/httpd/init/BUILD
+++ b/java/com/google/gerrit/httpd/init/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "init",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/index/BUILD b/java/com/google/gerrit/index/BUILD
index 7fcf342..5b6aae5 100644
--- a/java/com/google/gerrit/index/BUILD
+++ b/java/com/google/gerrit/index/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
QUERY_PARSE_EXCEPTION_SRCS = [
"query/QueryParseException.java",
"query/QueryRequiresAuthException.java",
diff --git a/java/com/google/gerrit/index/project/BUILD b/java/com/google/gerrit/index/project/BUILD
index f32d8c0..2c460fd 100644
--- a/java/com/google/gerrit/index/project/BUILD
+++ b/java/com/google/gerrit/index/project/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "project",
srcs = glob(["*.java"]),
diff --git a/java/com/google/gerrit/index/query/testing/BUILD b/java/com/google/gerrit/index/query/testing/BUILD
index ee346a8..1785f49 100644
--- a/java/com/google/gerrit/index/query/testing/BUILD
+++ b/java/com/google/gerrit/index/query/testing/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
package(
default_testonly = True,
default_visibility = ["//visibility:public"],
diff --git a/java/com/google/gerrit/jgit/BUILD b/java/com/google/gerrit/jgit/BUILD
index 2387614..e67ebfe 100644
--- a/java/com/google/gerrit/jgit/BUILD
+++ b/java/com/google/gerrit/jgit/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "jgit",
srcs = [
diff --git a/java/com/google/gerrit/json/BUILD b/java/com/google/gerrit/json/BUILD
index 030dddcb..439f23f 100644
--- a/java/com/google/gerrit/json/BUILD
+++ b/java/com/google/gerrit/json/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "json",
srcs = glob(["*.java"]),
diff --git a/java/com/google/gerrit/launcher/BUILD b/java/com/google/gerrit/launcher/BUILD
index bac0c53..15fa0ce 100644
--- a/java/com/google/gerrit/launcher/BUILD
+++ b/java/com/google/gerrit/launcher/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
# NOTE: GerritLauncher must be a single, self-contained class. Do not add any
# additional srcs or deps to this rule.
java_library(
diff --git a/java/com/google/gerrit/lifecycle/BUILD b/java/com/google/gerrit/lifecycle/BUILD
index 7ba6123..a3f3d81 100644
--- a/java/com/google/gerrit/lifecycle/BUILD
+++ b/java/com/google/gerrit/lifecycle/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "lifecycle",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/lucene/BUILD b/java/com/google/gerrit/lucene/BUILD
index fa4c923..40b2548 100644
--- a/java/com/google/gerrit/lucene/BUILD
+++ b/java/com/google/gerrit/lucene/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
QUERY_BUILDER = ["QueryBuilder.java"]
java_library(
diff --git a/java/com/google/gerrit/lucene/GerritIndexWriterConfig.java b/java/com/google/gerrit/lucene/GerritIndexWriterConfig.java
index 75e03e3..f6b2f0e 100644
--- a/java/com/google/gerrit/lucene/GerritIndexWriterConfig.java
+++ b/java/com/google/gerrit/lucene/GerritIndexWriterConfig.java
@@ -21,6 +21,7 @@
import com.google.gerrit.server.config.ConfigUtil;
import org.apache.lucene.analysis.CharArraySet;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.index.ConcurrentMergeScheduler;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.eclipse.jgit.lib.Config;
@@ -42,6 +43,21 @@
new IndexWriterConfig(analyzer)
.setOpenMode(OpenMode.CREATE_OR_APPEND)
.setCommitOnClose(true);
+
+ int maxMergeCount = cfg.getInt("index", name, "maxMergeCount", -1);
+ int maxThreadCount = cfg.getInt("index", name, "maxThreadCount", -1);
+ boolean enableAutoIOThrottle = cfg.getBoolean("index", name, "enableAutoIOThrottle", true);
+ if (maxMergeCount != -1 || maxThreadCount != -1 || !enableAutoIOThrottle) {
+ ConcurrentMergeScheduler mergeScheduler = new ConcurrentMergeScheduler();
+ if (maxMergeCount != -1 || maxThreadCount != -1) {
+ mergeScheduler.setMaxMergesAndThreads(maxMergeCount, maxThreadCount);
+ }
+ if (!enableAutoIOThrottle) {
+ mergeScheduler.disableAutoIOThrottle();
+ }
+ luceneConfig.setMergeScheduler(mergeScheduler);
+ }
+
double m = 1 << 20;
luceneConfig.setRAMBufferSizeMB(
cfg.getLong(
diff --git a/java/com/google/gerrit/mail/BUILD b/java/com/google/gerrit/mail/BUILD
index 90bb82c..6be5f0e 100644
--- a/java/com/google/gerrit/mail/BUILD
+++ b/java/com/google/gerrit/mail/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "mail",
srcs = glob(["*.java"]),
diff --git a/java/com/google/gerrit/metrics/BUILD b/java/com/google/gerrit/metrics/BUILD
index 68b29a4..9cc7654 100644
--- a/java/com/google/gerrit/metrics/BUILD
+++ b/java/com/google/gerrit/metrics/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "metrics",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/metrics/dropwizard/BUILD b/java/com/google/gerrit/metrics/dropwizard/BUILD
index 9adb375..4b3859f 100644
--- a/java/com/google/gerrit/metrics/dropwizard/BUILD
+++ b/java/com/google/gerrit/metrics/dropwizard/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "dropwizard",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/pgm/BUILD b/java/com/google/gerrit/pgm/BUILD
index 02c083c..ea6e9b7 100644
--- a/java/com/google/gerrit/pgm/BUILD
+++ b/java/com/google/gerrit/pgm/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
# TODO(davido): This indirection doesn't avoid unwanted depdencies
# in acceptance-framework and should be removed. Instead, provided_deps
# should be used, once https://github.com/bazelbuild/bazel/issues/1402
diff --git a/java/com/google/gerrit/pgm/http/BUILD b/java/com/google/gerrit/pgm/http/BUILD
index 838c614..34115ae 100644
--- a/java/com/google/gerrit/pgm/http/BUILD
+++ b/java/com/google/gerrit/pgm/http/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "http",
visibility = ["//visibility:public"],
diff --git a/java/com/google/gerrit/pgm/http/jetty/BUILD b/java/com/google/gerrit/pgm/http/jetty/BUILD
index a6a13dc..ea3afe1 100644
--- a/java/com/google/gerrit/pgm/http/jetty/BUILD
+++ b/java/com/google/gerrit/pgm/http/jetty/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "jetty",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/pgm/init/BUILD b/java/com/google/gerrit/pgm/init/BUILD
index b2a4d72..eb0d49e 100644
--- a/java/com/google/gerrit/pgm/init/BUILD
+++ b/java/com/google/gerrit/pgm/init/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "init",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/pgm/init/api/BUILD b/java/com/google/gerrit/pgm/init/api/BUILD
index 5b07fc6..19203fc 100644
--- a/java/com/google/gerrit/pgm/init/api/BUILD
+++ b/java/com/google/gerrit/pgm/init/api/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "api",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/pgm/util/BUILD b/java/com/google/gerrit/pgm/util/BUILD
index ffd1cbd..94798f7 100644
--- a/java/com/google/gerrit/pgm/util/BUILD
+++ b/java/com/google/gerrit/pgm/util/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "util",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/prettify/BUILD b/java/com/google/gerrit/prettify/BUILD
index 88b5b60..76afbe7 100644
--- a/java/com/google/gerrit/prettify/BUILD
+++ b/java/com/google/gerrit/prettify/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "server",
srcs = glob(["common/**/*.java"]),
diff --git a/java/com/google/gerrit/proto/BUILD b/java/com/google/gerrit/proto/BUILD
index 4f05bf6..98558c5 100644
--- a/java/com/google/gerrit/proto/BUILD
+++ b/java/com/google/gerrit/proto/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "proto",
srcs = ["Protos.java"],
diff --git a/java/com/google/gerrit/proto/testing/BUILD b/java/com/google/gerrit/proto/testing/BUILD
index 48115ff..acfa8f0 100644
--- a/java/com/google/gerrit/proto/testing/BUILD
+++ b/java/com/google/gerrit/proto/testing/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
package(default_testonly = True)
java_library(
diff --git a/java/com/google/gerrit/reviewdb/BUILD b/java/com/google/gerrit/reviewdb/BUILD
index 8c286ce..838aee8 100644
--- a/java/com/google/gerrit/reviewdb/BUILD
+++ b/java/com/google/gerrit/reviewdb/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
package(
default_visibility = ["//visibility:public"],
)
diff --git a/java/com/google/gerrit/reviewdb/client/RefNames.java b/java/com/google/gerrit/reviewdb/client/RefNames.java
index 3854310..115d6c2 100644
--- a/java/com/google/gerrit/reviewdb/client/RefNames.java
+++ b/java/com/google/gerrit/reviewdb/client/RefNames.java
@@ -128,6 +128,11 @@
return shard(id.changeId().get(), r).append('/').append(id.get()).toString();
}
+ public static String changeRefPrefix(Change.Id id) {
+ StringBuilder r = newStringBuilder().append(REFS_CHANGES);
+ return shard(id.get(), r).append('/').toString();
+ }
+
public static String robotCommentsRef(Change.Id id) {
StringBuilder r = newStringBuilder().append(REFS_CHANGES);
return shard(id.get(), r).append(ROBOT_COMMENTS_SUFFIX).toString();
diff --git a/java/com/google/gerrit/server/ApprovalsUtil.java b/java/com/google/gerrit/server/ApprovalsUtil.java
index 9befb46..c4f13f1 100644
--- a/java/com/google/gerrit/server/ApprovalsUtil.java
+++ b/java/com/google/gerrit/server/ApprovalsUtil.java
@@ -239,17 +239,28 @@
* @param notes change notes.
* @param update change update.
* @param wantCCs accounts to CC.
+ * @param keepExistingReviewers whether provided accounts that are already reviewer should be kept
+ * as reviewer or be downgraded to CC
* @return whether a change was made.
*/
public Collection<Account.Id> addCcs(
- ChangeNotes notes, ChangeUpdate update, Collection<Account.Id> wantCCs) {
- return addCcs(update, wantCCs, notes.load().getReviewers());
+ ChangeNotes notes,
+ ChangeUpdate update,
+ Collection<Account.Id> wantCCs,
+ boolean keepExistingReviewers) {
+ return addCcs(update, wantCCs, notes.load().getReviewers(), keepExistingReviewers);
}
private Collection<Account.Id> addCcs(
- ChangeUpdate update, Collection<Account.Id> wantCCs, ReviewerSet existingReviewers) {
+ ChangeUpdate update,
+ Collection<Account.Id> wantCCs,
+ ReviewerSet existingReviewers,
+ boolean keepExistingReviewers) {
Set<Account.Id> need = new LinkedHashSet<>(wantCCs);
- need.removeAll(existingReviewers.all());
+ need.removeAll(existingReviewers.byState(CC));
+ if (keepExistingReviewers) {
+ need.removeAll(existingReviewers.byState(REVIEWER));
+ }
need.removeAll(update.getReviewers().keySet());
for (Account.Id account : need) {
update.putReviewer(account, CC);
diff --git a/java/com/google/gerrit/server/BUILD b/java/com/google/gerrit/server/BUILD
index 6d4dfcf..0992294 100644
--- a/java/com/google/gerrit/server/BUILD
+++ b/java/com/google/gerrit/server/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//tools/bzl:javadoc.bzl", "java_doc")
CONSTANTS_SRC = [
diff --git a/java/com/google/gerrit/server/account/AccountCacheImpl.java b/java/com/google/gerrit/server/account/AccountCacheImpl.java
index af8d8b0..fe386ee 100644
--- a/java/com/google/gerrit/server/account/AccountCacheImpl.java
+++ b/java/com/google/gerrit/server/account/AccountCacheImpl.java
@@ -59,7 +59,7 @@
return new CacheModule() {
@Override
protected void configure() {
- cache(BYID_NAME, Account.Id.class, new TypeLiteral<Optional<AccountState>>() {})
+ cache(BYID_NAME, Account.Id.class, new TypeLiteral<AccountState>() {})
.loader(ByIdLoader.class);
bind(AccountCacheImpl.class);
@@ -69,13 +69,13 @@
}
private final ExternalIds externalIds;
- private final LoadingCache<Account.Id, Optional<AccountState>> byId;
+ private final LoadingCache<Account.Id, AccountState> byId;
private final ExecutorService executor;
@Inject
AccountCacheImpl(
ExternalIds externalIds,
- @Named(BYID_NAME) LoadingCache<Account.Id, Optional<AccountState>> byId,
+ @Named(BYID_NAME) LoadingCache<Account.Id, AccountState> byId,
@FanOutExecutor ExecutorService executor) {
this.externalIds = externalIds;
this.byId = byId;
@@ -85,9 +85,11 @@
@Override
public AccountState getEvenIfMissing(Account.Id accountId) {
try {
- return byId.get(accountId).orElse(missing(accountId));
+ return byId.get(accountId);
} catch (ExecutionException e) {
- logger.atWarning().withCause(e).log("Cannot load AccountState for %s", accountId);
+ if (!(e.getCause() instanceof AccountNotFoundException)) {
+ logger.atWarning().withCause(e).log("Cannot load AccountState for %s", accountId);
+ }
return missing(accountId);
}
}
@@ -95,9 +97,11 @@
@Override
public Optional<AccountState> get(Account.Id accountId) {
try {
- return byId.get(accountId);
+ return Optional.ofNullable(byId.get(accountId));
} catch (ExecutionException e) {
- logger.atWarning().withCause(e).log("Cannot load AccountState for ID %s", accountId);
+ if (!(e.getCause() instanceof AccountNotFoundException)) {
+ logger.atWarning().withCause(e).log("Cannot load AccountState for %s", accountId);
+ }
return Optional.empty();
}
}
@@ -107,10 +111,10 @@
Map<Account.Id, AccountState> accountStates = new HashMap<>(accountIds.size());
List<Callable<Optional<AccountState>>> callables = new ArrayList<>();
for (Account.Id accountId : accountIds) {
- Optional<AccountState> state = byId.getIfPresent(accountId);
+ AccountState state = byId.getIfPresent(accountId);
if (state != null) {
// The value is in-memory, so we just get the state
- state.ifPresent(s -> accountStates.put(accountId, s));
+ accountStates.put(accountId, state);
} else {
// Queue up a callable so that we can load accounts in parallel
callables.add(() -> get(accountId));
@@ -170,7 +174,7 @@
return AccountState.forAccount(account.build());
}
- static class ByIdLoader extends CacheLoader<Account.Id, Optional<AccountState>> {
+ static class ByIdLoader extends CacheLoader<Account.Id, AccountState> {
private final Accounts accounts;
@Inject
@@ -179,12 +183,23 @@
}
@Override
- public Optional<AccountState> load(Account.Id who) throws Exception {
+ public AccountState load(Account.Id who) throws Exception {
try (TraceTimer timer =
TraceContext.newTimer(
"Loading account", Metadata.builder().accountId(who.get()).build())) {
- return accounts.get(who);
+ return accounts
+ .get(who)
+ .orElseThrow(() -> new AccountNotFoundException(who + " not found"));
}
}
}
+
+ /** Signals that the account was not found in the primary storage. */
+ private static class AccountNotFoundException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public AccountNotFoundException(String message) {
+ super(message);
+ }
+ }
}
diff --git a/java/com/google/gerrit/server/account/externalids/testing/BUILD b/java/com/google/gerrit/server/account/externalids/testing/BUILD
index ec98ec8..6e59d22 100644
--- a/java/com/google/gerrit/server/account/externalids/testing/BUILD
+++ b/java/com/google/gerrit/server/account/externalids/testing/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "testing",
testonly = 1,
diff --git a/java/com/google/gerrit/server/api/BUILD b/java/com/google/gerrit/server/api/BUILD
index b9e26de..459c16a 100644
--- a/java/com/google/gerrit/server/api/BUILD
+++ b/java/com/google/gerrit/server/api/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "api",
srcs = glob(
diff --git a/java/com/google/gerrit/server/audit/BUILD b/java/com/google/gerrit/server/audit/BUILD
index 71cd3a1..5c2a40a 100644
--- a/java/com/google/gerrit/server/audit/BUILD
+++ b/java/com/google/gerrit/server/audit/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "audit",
srcs = glob(
diff --git a/java/com/google/gerrit/server/cache/h2/BUILD b/java/com/google/gerrit/server/cache/h2/BUILD
index f85b498..79baefc 100644
--- a/java/com/google/gerrit/server/cache/h2/BUILD
+++ b/java/com/google/gerrit/server/cache/h2/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "h2",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/server/cache/mem/BUILD b/java/com/google/gerrit/server/cache/mem/BUILD
index 4106714..eb0695e 100644
--- a/java/com/google/gerrit/server/cache/mem/BUILD
+++ b/java/com/google/gerrit/server/cache/mem/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "mem",
srcs = glob(["*.java"]),
diff --git a/java/com/google/gerrit/server/cache/serialize/BUILD b/java/com/google/gerrit/server/cache/serialize/BUILD
index a3a2054..3547605 100644
--- a/java/com/google/gerrit/server/cache/serialize/BUILD
+++ b/java/com/google/gerrit/server/cache/serialize/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "serialize",
srcs = glob(["*.java"]),
diff --git a/java/com/google/gerrit/server/cache/testing/BUILD b/java/com/google/gerrit/server/cache/testing/BUILD
index 16cbe17..09f698c 100644
--- a/java/com/google/gerrit/server/cache/testing/BUILD
+++ b/java/com/google/gerrit/server/cache/testing/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
package(default_testonly = True)
java_library(
diff --git a/java/com/google/gerrit/server/change/AddReviewersOp.java b/java/com/google/gerrit/server/change/AddReviewersOp.java
index a8ebcb2..15d8c60 100644
--- a/java/com/google/gerrit/server/change/AddReviewersOp.java
+++ b/java/com/google/gerrit/server/change/AddReviewersOp.java
@@ -64,10 +64,14 @@
* @param accountIds account IDs to add.
* @param addresses email addresses to add.
* @param state resulting reviewer state.
+ * @param forGroup whether this reviewer addition adds accounts for a group
* @return batch update operation.
*/
AddReviewersOp create(
- Set<Account.Id> accountIds, Collection<Address> addresses, ReviewerState state);
+ Set<Account.Id> accountIds,
+ Collection<Address> addresses,
+ ReviewerState state,
+ boolean forGroup);
}
@AutoValue
@@ -107,6 +111,7 @@
private final Set<Account.Id> accountIds;
private final Collection<Address> addresses;
private final ReviewerState state;
+ private final boolean forGroup;
// Unlike addedCCs, addedReviewers is a PatchSetApproval because the AddReviewerResult returned
// via the REST API is supposed to include vote information.
@@ -130,7 +135,8 @@
AddReviewersEmail addReviewersEmail,
@Assisted Set<Account.Id> accountIds,
@Assisted Collection<Address> addresses,
- @Assisted ReviewerState state) {
+ @Assisted ReviewerState state,
+ @Assisted boolean forGroup) {
checkArgument(state == REVIEWER || state == CC, "must be %s or %s: %s", REVIEWER, CC, state);
this.approvalsUtil = approvalsUtil;
this.psUtil = psUtil;
@@ -142,6 +148,7 @@
this.accountIds = accountIds;
this.addresses = addresses;
this.state = state;
+ this.forGroup = forGroup;
}
// TODO(dborowitz): This mutable setter is ugly, but a) it's less ugly than adding boolean args
@@ -162,7 +169,7 @@
if (state == CC) {
addedCCs =
approvalsUtil.addCcs(
- ctx.getNotes(), ctx.getUpdate(change.currentPatchSetId()), accountIds);
+ ctx.getNotes(), ctx.getUpdate(change.currentPatchSetId()), accountIds, forGroup);
} else {
addedReviewers =
approvalsUtil.addReviewers(
@@ -174,12 +181,11 @@
}
}
- ImmutableList<Address> addressesToAdd = ImmutableList.of();
ReviewerStateInternal internalState = ReviewerStateInternal.fromReviewerState(state);
// TODO(dborowitz): This behavior should live in ApprovalsUtil or something, like addCcs does.
ImmutableSet<Address> existing = ctx.getNotes().getReviewersByEmail().byState(internalState);
- addressesToAdd =
+ ImmutableList<Address> addressesToAdd =
addresses.stream().filter(a -> !existing.contains(a)).collect(toImmutableList());
if (state == CC) {
diff --git a/java/com/google/gerrit/server/change/DeleteChangeOp.java b/java/com/google/gerrit/server/change/DeleteChangeOp.java
index 37ac713..bc49ecf 100644
--- a/java/com/google/gerrit/server/change/DeleteChangeOp.java
+++ b/java/com/google/gerrit/server/change/DeleteChangeOp.java
@@ -14,6 +14,9 @@
package com.google.gerrit.server.change;
+import static com.google.common.flogger.LazyArgs.lazy;
+
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -37,6 +40,8 @@
import org.eclipse.jgit.revwalk.RevWalk;
public class DeleteChangeOp implements BatchUpdateOp {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
public interface Factory {
DeleteChangeOp create(Change.Id id);
}
@@ -73,6 +78,17 @@
// still part of the database.
cleanUpReferences(id);
+ logger.atFine().log(
+ "Deleting change %s, current patch set %d is commit %s",
+ id,
+ ctx.getChange().currentPatchSetId().get(),
+ lazy(
+ () ->
+ patchSets.stream()
+ .filter(p -> p.number() == ctx.getChange().currentPatchSetId().get())
+ .findAny()
+ .map(p -> p.commitId().name())
+ .orElse("n/a")));
ctx.deleteChange();
changeDeleted.fire(ctx.getChange(), ctx.getAccount(), ctx.getWhen());
return true;
@@ -112,10 +128,9 @@
@Override
public void updateRepo(RepoContext ctx) throws IOException {
- String prefix = PatchSet.id(id, 1).toRefName();
- prefix = prefix.substring(0, prefix.length() - 1);
- for (Map.Entry<String, ObjectId> e : ctx.getRepoView().getRefs(prefix).entrySet()) {
- removeRef(ctx, e, prefix);
+ String changeRefPrefix = RefNames.changeRefPrefix(id);
+ for (Map.Entry<String, ObjectId> e : ctx.getRepoView().getRefs(changeRefPrefix).entrySet()) {
+ removeRef(ctx, e, changeRefPrefix);
}
removeUserEdits(ctx);
}
diff --git a/java/com/google/gerrit/server/change/ReviewerAdder.java b/java/com/google/gerrit/server/change/ReviewerAdder.java
index 1c4f63c..9e76888 100644
--- a/java/com/google/gerrit/server/change/ReviewerAdder.java
+++ b/java/com/google/gerrit/server/change/ReviewerAdder.java
@@ -237,7 +237,8 @@
revision.getUser(),
ImmutableSet.of(user.getAccountId()),
null,
- true);
+ true,
+ false);
}
@Nullable
@@ -260,7 +261,13 @@
if (isValidReviewer(notes.getChange().getDest(), reviewerUser.getAccount())) {
return new ReviewerAddition(
- input, notes, user, ImmutableSet.of(reviewerUser.getAccountId()), null, exactMatchFound);
+ input,
+ notes,
+ user,
+ ImmutableSet.of(reviewerUser.getAccountId()),
+ null,
+ exactMatchFound,
+ false);
}
return fail(
input,
@@ -344,7 +351,7 @@
}
}
- return new ReviewerAddition(input, notes, user, reviewers, null, true);
+ return new ReviewerAddition(input, notes, user, reviewers, null, true, true);
}
@Nullable
@@ -366,7 +373,7 @@
FailureType.NOT_FOUND,
MessageFormat.format(ChangeMessages.get().reviewerInvalid, input.reviewer));
}
- return new ReviewerAddition(input, notes, user, null, ImmutableList.of(adr), true);
+ return new ReviewerAddition(input, notes, user, null, ImmutableList.of(adr), true, false);
}
private boolean isValidReviewer(BranchNameKey branch, Account member)
@@ -421,7 +428,8 @@
CurrentUser caller,
@Nullable Iterable<Account.Id> reviewers,
@Nullable Iterable<Address> reviewersByEmail,
- boolean exactMatchFound) {
+ boolean exactMatchFound,
+ boolean forGroup) {
checkArgument(
reviewers != null || reviewersByEmail != null,
"must have either reviewers or reviewersByEmail");
@@ -435,7 +443,7 @@
this.reviewersByEmail =
reviewersByEmail == null ? ImmutableSet.of() : ImmutableSet.copyOf(reviewersByEmail);
this.caller = caller.asIdentifiedUser();
- op = addReviewersOpFactory.create(this.reviewers, this.reviewersByEmail, state());
+ op = addReviewersOpFactory.create(this.reviewers, this.reviewersByEmail, state(), forGroup);
this.exactMatchFound = exactMatchFound;
}
diff --git a/java/com/google/gerrit/server/edit/ChangeEditModifier.java b/java/com/google/gerrit/server/edit/ChangeEditModifier.java
index fcd38c3..661e376 100644
--- a/java/com/google/gerrit/server/edit/ChangeEditModifier.java
+++ b/java/com/google/gerrit/server/edit/ChangeEditModifier.java
@@ -371,7 +371,7 @@
if (optionalChangeEdit.isPresent()) {
ChangeEdit changeEdit = optionalChangeEdit.get();
newTreeId = merge(repository, changeEdit, newTreeId);
- if (ObjectId.equals(newTreeId, changeEdit.getEditCommit().getTree())) {
+ if (ObjectId.isEqual(newTreeId, changeEdit.getEditCommit().getTree())) {
// Modifications are already contained in the change edit.
return changeEdit;
}
@@ -474,7 +474,7 @@
treeCreator.addTreeModifications(treeModifications);
ObjectId newTreeId = treeCreator.createNewTreeAndGetId(repository);
- if (ObjectId.equals(newTreeId, baseCommit.getTree())) {
+ if (ObjectId.isEqual(newTreeId, baseCommit.getTree())) {
throw new InvalidChangeOperationException("no changes were made");
}
return newTreeId;
diff --git a/java/com/google/gerrit/server/edit/tree/ChangeFileContentModification.java b/java/com/google/gerrit/server/edit/tree/ChangeFileContentModification.java
index d91e2e8..0adacd8 100644
--- a/java/com/google/gerrit/server/edit/tree/ChangeFileContentModification.java
+++ b/java/com/google/gerrit/server/edit/tree/ChangeFileContentModification.java
@@ -23,6 +23,7 @@
import com.google.gerrit.extensions.restapi.RawInput;
import java.io.IOException;
import java.io.InputStream;
+import java.time.Instant;
import java.util.Collections;
import java.util.List;
import org.eclipse.jgit.dircache.DirCacheEditor;
@@ -79,7 +80,7 @@
try {
if (dirCacheEntry.getFileMode() == FileMode.GITLINK) {
dirCacheEntry.setLength(0);
- dirCacheEntry.setLastModified(0);
+ dirCacheEntry.setLastModified(Instant.EPOCH);
ObjectId newObjectId = ObjectId.fromString(getNewContentBytes(), 0);
dirCacheEntry.setObjectId(newObjectId);
} else {
diff --git a/java/com/google/gerrit/server/git/receive/BUILD b/java/com/google/gerrit/server/git/receive/BUILD
index a4f4d93..3f7d864 100644
--- a/java/com/google/gerrit/server/git/receive/BUILD
+++ b/java/com/google/gerrit/server/git/receive/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "receive",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 38e60d4..13ec5a8 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -377,6 +377,7 @@
private MessageSender messageSender;
private ResultChangeIds resultChangeIds;
+ private Map<String, String> loggingTags;
@Inject
ReceiveCommits(
@@ -495,6 +496,7 @@
// Handles for outputting back over the wire to the end user.
this.messageSender = messageSender != null ? messageSender : new ReceivePackMessageSender();
this.resultChangeIds = resultChangeIds;
+ this.loggingTags = new HashMap<>();
}
void init() {
@@ -522,11 +524,20 @@
}
void sendMessages() {
- for (ValidationMessage m : messages) {
- String msg = m.getType().getPrefix() + m.getMessage();
+ try (TraceContext traceContext =
+ TraceContext.newTrace(
+ loggingTags.containsKey(RequestId.Type.TRACE_ID.name()),
+ loggingTags.get(RequestId.Type.TRACE_ID.name()),
+ (tagName, traceId) -> {})) {
+ loggingTags.forEach((tagName, tagValue) -> traceContext.addTag(tagName, tagValue));
- // Avoid calling sendError which will add its own error: prefix.
- messageSender.sendMessage(msg);
+ for (ValidationMessage m : messages) {
+ String msg = m.getType().getPrefix() + m.getMessage();
+ logger.atFine().log("Sending message: %s", msg);
+
+ // Avoid calling sendError which will add its own error: prefix.
+ messageSender.sendMessage(msg);
+ }
}
}
@@ -566,6 +577,8 @@
commandProgress.end();
progress.end();
+ loggingTags.putAll(traceContext.getTags());
+ logger.atFine().log("Processing commands done.");
}
}
@@ -3396,6 +3409,7 @@
}
private static void reject(ReceiveCommand cmd, String why) {
+ logger.atFine().log("Rejecting command '%s': %s", cmd, why);
cmd.setResult(REJECTED_OTHER_REASON, why);
}
diff --git a/java/com/google/gerrit/server/git/receive/testing/BUILD b/java/com/google/gerrit/server/git/receive/testing/BUILD
index 82cd14b..a5e7998 100644
--- a/java/com/google/gerrit/server/git/receive/testing/BUILD
+++ b/java/com/google/gerrit/server/git/receive/testing/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "testing",
testonly = 1,
diff --git a/java/com/google/gerrit/server/group/db/testing/BUILD b/java/com/google/gerrit/server/group/db/testing/BUILD
index c13abba..b5d5a43 100644
--- a/java/com/google/gerrit/server/group/db/testing/BUILD
+++ b/java/com/google/gerrit/server/group/db/testing/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
package(default_visibility = ["//visibility:public"])
java_library(
diff --git a/java/com/google/gerrit/server/group/testing/BUILD b/java/com/google/gerrit/server/group/testing/BUILD
index 3ef712c..9b6d8de 100644
--- a/java/com/google/gerrit/server/group/testing/BUILD
+++ b/java/com/google/gerrit/server/group/testing/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
package(default_visibility = ["//visibility:public"])
java_library(
diff --git a/java/com/google/gerrit/server/ioutil/BUILD b/java/com/google/gerrit/server/ioutil/BUILD
index ea91929..ed58d5b 100644
--- a/java/com/google/gerrit/server/ioutil/BUILD
+++ b/java/com/google/gerrit/server/ioutil/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "ioutil",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/server/logging/BUILD b/java/com/google/gerrit/server/logging/BUILD
index f78ff5f..c214d69 100644
--- a/java/com/google/gerrit/server/logging/BUILD
+++ b/java/com/google/gerrit/server/logging/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "logging",
srcs = glob(
diff --git a/java/com/google/gerrit/server/logging/TraceContext.java b/java/com/google/gerrit/server/logging/TraceContext.java
index b597a51..15a3532 100644
--- a/java/com/google/gerrit/server/logging/TraceContext.java
+++ b/java/com/google/gerrit/server/logging/TraceContext.java
@@ -19,6 +19,7 @@
import com.google.common.base.Stopwatch;
import com.google.common.base.Strings;
import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Table;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
@@ -231,6 +232,12 @@
return this;
}
+ public ImmutableMap<String, String> getTags() {
+ ImmutableMap.Builder<String, String> tagMap = ImmutableMap.builder();
+ tags.cellSet().forEach(c -> tagMap.put(c.getRowKey(), c.getColumnKey()));
+ return tagMap.build();
+ }
+
public TraceContext addPluginTag(String pluginName) {
return addTag(PLUGIN_TAG, pluginName);
}
diff --git a/java/com/google/gerrit/server/patch/PatchListLoader.java b/java/com/google/gerrit/server/patch/PatchListLoader.java
index 08de537..b639f96 100644
--- a/java/com/google/gerrit/server/patch/PatchListLoader.java
+++ b/java/com/google/gerrit/server/patch/PatchListLoader.java
@@ -314,12 +314,12 @@
}
private static boolean areParentChild(RevCommit commitA, RevCommit commitB) {
- return ObjectId.equals(commitA.getParent(0), commitB)
- || ObjectId.equals(commitB.getParent(0), commitA);
+ return ObjectId.isEqual(commitA.getParent(0), commitB)
+ || ObjectId.isEqual(commitB.getParent(0), commitA);
}
private static boolean haveCommonParent(RevCommit commitA, RevCommit commitB) {
- return ObjectId.equals(commitA.getParent(0), commitB.getParent(0));
+ return ObjectId.isEqual(commitA.getParent(0), commitB.getParent(0));
}
private static Set<String> getTouchedFilePaths(PatchListEntry patchListEntry) {
diff --git a/java/com/google/gerrit/server/project/testing/BUILD b/java/com/google/gerrit/server/project/testing/BUILD
index f221e00..968e3da 100644
--- a/java/com/google/gerrit/server/project/testing/BUILD
+++ b/java/com/google/gerrit/server/project/testing/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "project-test-util",
testonly = True,
diff --git a/java/com/google/gerrit/server/restapi/BUILD b/java/com/google/gerrit/server/restapi/BUILD
index f2d6e0f..9a2d11b 100644
--- a/java/com/google/gerrit/server/restapi/BUILD
+++ b/java/com/google/gerrit/server/restapi/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
package(
default_visibility = ["//visibility:public"],
)
diff --git a/java/com/google/gerrit/server/schema/BUILD b/java/com/google/gerrit/server/schema/BUILD
index aa552ed..fe07791 100644
--- a/java/com/google/gerrit/server/schema/BUILD
+++ b/java/com/google/gerrit/server/schema/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "schema",
srcs = glob(
diff --git a/java/com/google/gerrit/server/schema/testing/BUILD b/java/com/google/gerrit/server/schema/testing/BUILD
index c520f43..d641c47 100644
--- a/java/com/google/gerrit/server/schema/testing/BUILD
+++ b/java/com/google/gerrit/server/schema/testing/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
package(default_visibility = ["//visibility:public"])
java_library(
diff --git a/java/com/google/gerrit/server/securestore/testing/BUILD b/java/com/google/gerrit/server/securestore/testing/BUILD
index 9b76b9e..c2582b9 100644
--- a/java/com/google/gerrit/server/securestore/testing/BUILD
+++ b/java/com/google/gerrit/server/securestore/testing/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
package(default_testonly = True)
java_library(
diff --git a/java/com/google/gerrit/server/util/git/BUILD b/java/com/google/gerrit/server/util/git/BUILD
index 81ca9cd..a8ae918 100644
--- a/java/com/google/gerrit/server/util/git/BUILD
+++ b/java/com/google/gerrit/server/util/git/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "git",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/server/util/time/BUILD b/java/com/google/gerrit/server/util/time/BUILD
index 710a6b1..d00b42d 100644
--- a/java/com/google/gerrit/server/util/time/BUILD
+++ b/java/com/google/gerrit/server/util/time/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "time",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/sshd/BUILD b/java/com/google/gerrit/sshd/BUILD
index 3a69554..33bdf69 100644
--- a/java/com/google/gerrit/sshd/BUILD
+++ b/java/com/google/gerrit/sshd/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "sshd",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/sshd/commands/FlushCaches.java b/java/com/google/gerrit/sshd/commands/FlushCaches.java
index df56cf4..98562b0 100644
--- a/java/com/google/gerrit/sshd/commands/FlushCaches.java
+++ b/java/com/google/gerrit/sshd/commands/FlushCaches.java
@@ -89,7 +89,8 @@
@SuppressWarnings("unchecked")
private void doList() {
for (String name :
- (List<String>) listCaches.setFormat(OutputFormat.LIST).apply(new ConfigResource())) {
+ (List<String>)
+ listCaches.setFormat(OutputFormat.LIST).apply(new ConfigResource()).value()) {
stderr.print(name);
stderr.print('\n');
}
diff --git a/java/com/google/gerrit/sshd/commands/ShowCaches.java b/java/com/google/gerrit/sshd/commands/ShowCaches.java
index c19e790..3c617b0 100644
--- a/java/com/google/gerrit/sshd/commands/ShowCaches.java
+++ b/java/com/google/gerrit/sshd/commands/ShowCaches.java
@@ -194,7 +194,8 @@
private Collection<CacheInfo> getCaches() {
@SuppressWarnings("unchecked")
- Map<String, CacheInfo> caches = (Map<String, CacheInfo>) listCaches.apply(new ConfigResource());
+ Map<String, CacheInfo> caches =
+ (Map<String, CacheInfo>) listCaches.apply(new ConfigResource()).value();
for (Map.Entry<String, CacheInfo> entry : caches.entrySet()) {
CacheInfo cache = entry.getValue();
cache.name = entry.getKey();
diff --git a/java/com/google/gerrit/sshd/commands/UploadArchive.java b/java/com/google/gerrit/sshd/commands/UploadArchive.java
index a58e472..c25a1a8 100644
--- a/java/com/google/gerrit/sshd/commands/UploadArchive.java
+++ b/java/com/google/gerrit/sshd/commands/UploadArchive.java
@@ -139,7 +139,7 @@
PacketLineIn packetIn = new PacketLineIn(in);
for (; ; ) {
String s = packetIn.readString();
- if (isPacketLineEnd(s)) {
+ if (PacketLineIn.isEnd(s)) {
break;
}
if (!s.startsWith(argCmd)) {
@@ -163,12 +163,6 @@
}
}
- // JGit API depends on reference equality with sentinel.
- @SuppressWarnings({"ReferenceEquality", "StringEquality"})
- private static boolean isPacketLineEnd(String s) {
- return s == PacketLineIn.END;
- }
-
@Override
protected void runImpl() throws IOException, PermissionBackendException, Failure {
PacketLineOut packetOut = new PacketLineOut(out);
diff --git a/java/com/google/gerrit/testing/BUILD b/java/com/google/gerrit/testing/BUILD
index 27065aa..f5298ea 100644
--- a/java/com/google/gerrit/testing/BUILD
+++ b/java/com/google/gerrit/testing/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "gerrit-test-util",
testonly = True,
diff --git a/java/com/google/gerrit/truth/BUILD b/java/com/google/gerrit/truth/BUILD
index 4727da1..7c0e743 100644
--- a/java/com/google/gerrit/truth/BUILD
+++ b/java/com/google/gerrit/truth/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "truth",
testonly = True,
diff --git a/java/com/google/gerrit/util/cli/BUILD b/java/com/google/gerrit/util/cli/BUILD
index b9b9bba..e4f2c21 100644
--- a/java/com/google/gerrit/util/cli/BUILD
+++ b/java/com/google/gerrit/util/cli/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "cli",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/util/http/BUILD b/java/com/google/gerrit/util/http/BUILD
index 30d3adc..5ecb7a1 100644
--- a/java/com/google/gerrit/util/http/BUILD
+++ b/java/com/google/gerrit/util/http/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "http",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/util/ssl/BUILD b/java/com/google/gerrit/util/ssl/BUILD
index 4f65b61..e0641c7 100644
--- a/java/com/google/gerrit/util/ssl/BUILD
+++ b/java/com/google/gerrit/util/ssl/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "ssl",
srcs = glob(["**/*.java"]),
diff --git a/java/gerrit/BUILD b/java/gerrit/BUILD
index f416f11..d7e2306 100644
--- a/java/gerrit/BUILD
+++ b/java/gerrit/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "prolog-predicates",
srcs = glob(["**/*.java"]),
diff --git a/java/org/apache/commons/net/BUILD b/java/org/apache/commons/net/BUILD
index 4951933..c322ecd 100644
--- a/java/org/apache/commons/net/BUILD
+++ b/java/org/apache/commons/net/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "net",
srcs = glob(["**/*.java"]),
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index c1e83d4..48c9995 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -225,7 +225,7 @@
@Inject
@Named("accounts")
- private LoadingCache<Account.Id, Optional<AccountState>> accountsCache;
+ private LoadingCache<Account.Id, AccountState> accountsCache;
@Inject private AccountOperations accountOperations;
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index dda9b88..9fefbe9 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -454,7 +454,7 @@
}
@Test
- public void pendingReviewersInNoteDb() throws Exception {
+ public void pendingReviewers() throws Exception {
ConfigInput conf = new ConfigInput();
conf.enableReviewerByEmail = InheritableBoolean.TRUE;
gApi.projects().name(project.get()).config(conf);
@@ -1918,10 +1918,7 @@
assertMailReplyTo(m, admin.email());
ChangeInfo c = gApi.changes().id(r.getChangeId()).get();
- // When NoteDb is enabled adding a reviewer records that user as reviewer
- // in NoteDb. When NoteDb is disabled adding a reviewer results in a dummy 0
- // approval on the change which is treated as CC when the ChangeInfo is
- // created.
+ // Adding a reviewer records that user as reviewer.
Collection<AccountInfo> reviewers = c.reviewers.get(REVIEWER);
assertThat(reviewers).isNotNull();
assertThat(reviewers).hasSize(1);
@@ -2033,10 +2030,7 @@
assertMailReplyTo(m, email);
ChangeInfo c = gApi.changes().id(r.getChangeId()).get();
- // When NoteDb is enabled adding a reviewer records that user as reviewer
- // in NoteDb. When NoteDb is disabled adding a reviewer results in a dummy 0
- // approval on the change which is treated as CC when the ChangeInfo is
- // created.
+ // Adding a reviewer records that user as reviewer.
Collection<AccountInfo> reviewers = c.reviewers.get(REVIEWER);
assertThat(reviewers).isNotNull();
assertThat(reviewers).hasSize(1);
@@ -2097,10 +2091,7 @@
assertMailReplyTo(m, myGroupUserEmail);
ChangeInfo c = gApi.changes().id(r.getChangeId()).get();
- // When NoteDb is enabled adding a reviewer records that user as reviewer
- // in NoteDb. When NoteDb is disabled adding a reviewer results in a dummy 0
- // approval on the change which is treated as CC when the ChangeInfo is
- // created.
+ // Adding a reviewer records that user as reviewer.
Collection<AccountInfo> reviewers = c.reviewers.get(REVIEWER);
assertThat(reviewers).isNotNull();
assertThat(reviewers).hasSize(1);
@@ -2128,10 +2119,7 @@
// There should be no email notification when adding self
assertThat(sender.getMessages()).isEmpty();
- // When NoteDb is enabled adding a reviewer records that user as reviewer
- // in NoteDb. When NoteDb is disabled adding a reviewer results in a dummy 0
- // approval on the change which is treated as CC when the ChangeInfo is
- // created.
+ // Adding a reviewer records that user as reviewer.
ChangeInfo c = gApi.changes().id(r.getChangeId()).get();
Collection<AccountInfo> reviewers = c.reviewers.get(REVIEWER);
assertThat(reviewers).isNotNull();
@@ -2187,7 +2175,7 @@
.containsExactly(user.id().get());
// Further test: remove the vote, then comment again. The user should be
- // implicitly re-added to the ReviewerSet, as a CC if we're using NoteDb.
+ // implicitly re-added to the ReviewerSet, as a CC.
requestScopeOperations.setApiUser(admin.id());
gApi.changes().id(r.getChangeId()).reviewer(user.id().toString()).remove();
c = gApi.changes().id(r.getChangeId()).get();
@@ -3177,7 +3165,7 @@
}
@Test
- public void noteDbCommitsOnPatchSetCreation() throws Exception {
+ public void commitsOnPatchSetCreation() throws Exception {
PushOneCommit.Result r = createChange();
pushFactory
.create(admin.newIdent(), testRepo, PushOneCommit.SUBJECT, "b.txt", "4711", r.getChangeId())
@@ -4366,10 +4354,7 @@
public boolean updateChange(ChangeContext ctx) throws Exception {
Change change = ctx.getChange();
- // Change status in database.
- change.setStatus(newStatus);
-
- // Change status in NoteDb.
+ // Change status.
PatchSet.Id currentPatchSetId = change.currentPatchSetId();
ctx.getUpdate(currentPatchSetId).setStatus(newStatus);
diff --git a/javatests/com/google/gerrit/acceptance/api/group/BUILD b/javatests/com/google/gerrit/acceptance/api/group/BUILD
index a12342a..e311e25 100644
--- a/javatests/com/google/gerrit/acceptance/api/group/BUILD
+++ b/javatests/com/google/gerrit/acceptance/api/group/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
acceptance_tests(
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index 89dbabf..27cc241 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -2201,6 +2201,17 @@
}
@Test
+ public void noEditAndUpdateAllUsersInSameChangeStack() throws Exception {
+ List<RevCommit> commits = createChanges(2, "refs/for/master");
+ String id2 = byCommit(commits.get(1)).change().getKey().get();
+ addDraft(id2, commits.get(1).name(), newDraft(FILE_NAME, 1, "comment2"));
+ // First change in stack unchanged.
+ RevCommit unChanged = commits.remove(0);
+ // Publishing draft comments on change 2 updates All-Users.
+ amendChanges(unChanged.toObjectId(), commits, "refs/for/master%publish-comments");
+ }
+
+ @Test
public void pushWithDraftOptionIsDisabledPerDefault() throws Exception {
for (String ref : ImmutableSet.of("refs/drafts/master", "refs/for/master%draft")) {
PushOneCommit.Result r = pushTo(ref);
diff --git a/javatests/com/google/gerrit/acceptance/git/BUILD b/javatests/com/google/gerrit/acceptance/git/BUILD
index 0de307a..dfaf9e3 100644
--- a/javatests/com/google/gerrit/acceptance/git/BUILD
+++ b/javatests/com/google/gerrit/acceptance/git/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
[acceptance_tests(
diff --git a/javatests/com/google/gerrit/acceptance/pgm/BUILD b/javatests/com/google/gerrit/acceptance/pgm/BUILD
index e0ed78a..d15c6ce 100644
--- a/javatests/com/google/gerrit/acceptance/pgm/BUILD
+++ b/javatests/com/google/gerrit/acceptance/pgm/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
acceptance_tests(
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/AccountAssert.java b/javatests/com/google/gerrit/acceptance/rest/account/AccountAssert.java
index 63e9ebf..c4418c0 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/AccountAssert.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/AccountAssert.java
@@ -25,9 +25,9 @@
public class AccountAssert {
public static void assertAccountInfo(TestAccount a, AccountInfo ai) {
- assertThat(a.id().get()).isEqualTo(ai._accountId);
- assertThat(a.fullName()).isEqualTo(ai.name);
- assertThat(a.email()).isEqualTo(ai.email);
+ assertThat(ai._accountId).isEqualTo(a.id().get());
+ assertThat(ai.name).isEqualTo(a.fullName());
+ assertThat(ai.email).isEqualTo(a.email());
}
public static void assertAccountInfos(List<TestAccount> expected, List<AccountInfo> actual) {
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/BUILD b/javatests/com/google/gerrit/acceptance/rest/account/BUILD
index 17a6053..e801dcc 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/BUILD
+++ b/javatests/com/google/gerrit/acceptance/rest/account/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
acceptance_tests(
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/BUILD b/javatests/com/google/gerrit/acceptance/rest/change/BUILD
index 9a65378..7ccf10f 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/BUILD
+++ b/javatests/com/google/gerrit/acceptance/rest/change/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
SUBMIT_UTIL_SRCS = glob(["AbstractSubmit*.java"])
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
index e300c91..96b96eb 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
@@ -16,6 +16,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
import static com.google.gerrit.extensions.client.ReviewerState.CC;
import static com.google.gerrit.extensions.client.ReviewerState.REMOVED;
@@ -390,8 +391,7 @@
assertThat(result.reviewers).isNotNull();
assertThat(result.reviewers).hasSize(2);
- // Verify reviewer and CC were added. If not in NoteDb read mode, both
- // parties will be returned as CCed.
+ // Verify reviewer and CC were added.
ChangeInfo c = gApi.changes().id(r.getChangeId()).get();
assertReviewers(c, REVIEWER, admin, user);
assertReviewers(c, CC, observer);
@@ -492,7 +492,7 @@
}
@Test
- public void noteDbAddReviewerToReviewerChangeInfo() throws Exception {
+ public void addReviewerToReviewerChangeInfo() throws Exception {
PushOneCommit.Result r = createChange();
String changeId = r.getChangeId();
AddReviewerInput in = new AddReviewerInput();
@@ -506,7 +506,7 @@
gApi.changes().id(changeId).current().review(ReviewInput.dislike());
requestScopeOperations.setApiUser(user.id());
- // NoteDb adds reviewer to a change on every review.
+ // By posting a review the user is added as reviewer.
gApi.changes().id(changeId).current().review(ReviewInput.dislike());
deleteReviewer(changeId, user).assertNoContent();
@@ -756,6 +756,81 @@
assertThat(gApi.changes().id(r.getChangeId()).addReviewer(input).ccs).isEmpty();
}
+ @Test
+ public void moveCcToReviewer() throws Exception {
+ // Create a change and add 'user' as CC.
+ String changeId = createChange().getChangeId();
+ AddReviewerInput reviewerInput = new AddReviewerInput();
+ reviewerInput.reviewer = user.email();
+ reviewerInput.state = ReviewerState.CC;
+ gApi.changes().id(changeId).addReviewer(reviewerInput);
+
+ // Verify that 'user' is a CC on the change and that there are no reviewers.
+ ChangeInfo c = gApi.changes().id(changeId).get();
+ Collection<AccountInfo> ccs = c.reviewers.get(CC);
+ assertThat(ccs).isNotNull();
+ assertThat(ccs).hasSize(1);
+ assertThat(ccs.iterator().next()._accountId).isEqualTo(user.id().get());
+ assertThat(c.reviewers.get(REVIEWER)).isNull();
+
+ // Move 'user' from CC to reviewer.
+ gApi.changes().id(changeId).addReviewer(user.id().toString());
+
+ // Verify that 'user' is a reviewer on the change now and that there are no CCs.
+ c = gApi.changes().id(changeId).get();
+ Collection<AccountInfo> reviewers = c.reviewers.get(REVIEWER);
+ assertThat(reviewers).isNotNull();
+ assertThat(reviewers).hasSize(1);
+ assertThat(reviewers.iterator().next()._accountId).isEqualTo(user.id().get());
+ assertThat(c.reviewers.get(CC)).isNull();
+ }
+
+ @Test
+ public void moveReviewerToCc() throws Exception {
+ // Allow everyone to approve changes.
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allowLabel("Code-Review").ref("refs/heads/*").group(REGISTERED_USERS).range(-2, 2))
+ .update();
+
+ // Create a change and add 'user' as reviewer.
+ String changeId = createChange().getChangeId();
+ gApi.changes().id(changeId).addReviewer(user.id().toString());
+
+ // Verify that 'user' is a reviewer on the change and that there are no CCs.
+ ChangeInfo c = gApi.changes().id(changeId).get();
+ Collection<AccountInfo> reviewers = c.reviewers.get(REVIEWER);
+ assertThat(reviewers).isNotNull();
+ assertThat(reviewers).hasSize(1);
+ assertThat(reviewers.iterator().next()._accountId).isEqualTo(user.id().get());
+ assertThat(c.reviewers.get(CC)).isNull();
+
+ // Let 'user' approve the change and verify that the change has the approval.
+ requestScopeOperations.setApiUser(user.id());
+ approve(changeId);
+ c = gApi.changes().id(changeId).get();
+ assertThat(c.labels.get("Code-Review").approved._accountId).isEqualTo(user.id().get());
+
+ // Move 'user' from reviewer to CC.
+ requestScopeOperations.setApiUser(admin.id());
+ AddReviewerInput reviewerInput = new AddReviewerInput();
+ reviewerInput.reviewer = user.id().toString();
+ reviewerInput.state = CC;
+ gApi.changes().id(changeId).addReviewer(reviewerInput);
+
+ // Verify that 'user' is a CC on the change now and that there are no reviewers.
+ c = gApi.changes().id(changeId).get();
+ Collection<AccountInfo> ccs = c.reviewers.get(CC);
+ assertThat(ccs).isNotNull();
+ assertThat(ccs).hasSize(1);
+ assertThat(ccs.iterator().next()._accountId).isEqualTo(user.id().get());
+ assertThat(c.reviewers.get(REVIEWER)).isNull();
+
+ // Verify that the approval of 'user' is still there.
+ assertThat(c.labels.get("Code-Review").approved._accountId).isEqualTo(user.id().get());
+ }
+
private void assertThatUserIsOnlyReviewer(String changeId) throws Exception {
AccountInfo userInfo = new AccountInfo(user.fullName(), user.getEmailAddress().getEmail());
userInfo._accountId = user.id().get();
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/BUILD b/javatests/com/google/gerrit/acceptance/rest/project/BUILD
index 131c24a..200b26a 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/BUILD
+++ b/javatests/com/google/gerrit/acceptance/rest/project/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
acceptance_tests(
diff --git a/javatests/com/google/gerrit/acceptance/rest/util/BUILD b/javatests/com/google/gerrit/acceptance/rest/util/BUILD
index cc72e8a..1d3fe65 100644
--- a/javatests/com/google/gerrit/acceptance/rest/util/BUILD
+++ b/javatests/com/google/gerrit/acceptance/rest/util/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "util",
testonly = True,
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/BUILD b/javatests/com/google/gerrit/acceptance/server/mail/BUILD
index e21789b..5d7e65e 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/BUILD
+++ b/javatests/com/google/gerrit/acceptance/server/mail/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
DEPS = [
diff --git a/javatests/com/google/gerrit/acceptance/ssh/BUILD b/javatests/com/google/gerrit/acceptance/ssh/BUILD
index 00a0914..5634322 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/BUILD
+++ b/javatests/com/google/gerrit/acceptance/ssh/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
java_library(
diff --git a/javatests/com/google/gerrit/elasticsearch/BUILD b/javatests/com/google/gerrit/elasticsearch/BUILD
index a2bd092..e50f2b5 100644
--- a/javatests/com/google/gerrit/elasticsearch/BUILD
+++ b/javatests/com/google/gerrit/elasticsearch/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//tools/bzl:junit.bzl", "junit_tests")
java_library(
diff --git a/javatests/com/google/gerrit/reviewdb/client/RefNamesTest.java b/javatests/com/google/gerrit/reviewdb/client/RefNamesTest.java
index 7f22275..17c3fdc 100644
--- a/javatests/com/google/gerrit/reviewdb/client/RefNamesTest.java
+++ b/javatests/com/google/gerrit/reviewdb/client/RefNamesTest.java
@@ -50,6 +50,9 @@
String robotCommentsRef = RefNames.robotCommentsRef(changeId);
assertThat(robotCommentsRef).isEqualTo("refs/changes/73/67473/robot-comments");
assertThat(RefNames.isNoteDbMetaRef(robotCommentsRef)).isTrue();
+
+ String changeRefPrefix = RefNames.changeRefPrefix(changeId);
+ assertThat(changeRefPrefix).isEqualTo("refs/changes/73/67473/");
}
@Test
diff --git a/javatests/com/google/gerrit/server/BUILD b/javatests/com/google/gerrit/server/BUILD
index f6ed5ef..1bb22e4 100644
--- a/javatests/com/google/gerrit/server/BUILD
+++ b/javatests/com/google/gerrit/server/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//tools/bzl:junit.bzl", "junit_tests")
CUSTOM_TRUTH_SUBJECTS = glob([
diff --git a/javatests/com/google/gerrit/server/query/account/BUILD b/javatests/com/google/gerrit/server/query/account/BUILD
index e41d390..7b72f4e 100644
--- a/javatests/com/google/gerrit/server/query/account/BUILD
+++ b/javatests/com/google/gerrit/server/query/account/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//tools/bzl:junit.bzl", "junit_tests")
ABSTRACT_QUERY_TEST = ["AbstractQueryAccountsTest.java"]
diff --git a/javatests/com/google/gerrit/server/query/change/BUILD b/javatests/com/google/gerrit/server/query/change/BUILD
index a128593..67b7c47 100644
--- a/javatests/com/google/gerrit/server/query/change/BUILD
+++ b/javatests/com/google/gerrit/server/query/change/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//tools/bzl:junit.bzl", "junit_tests")
ABSTRACT_QUERY_TEST = ["AbstractQueryChangesTest.java"]
diff --git a/javatests/com/google/gerrit/server/query/group/BUILD b/javatests/com/google/gerrit/server/query/group/BUILD
index 3f147c9..1271f4e 100644
--- a/javatests/com/google/gerrit/server/query/group/BUILD
+++ b/javatests/com/google/gerrit/server/query/group/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//tools/bzl:junit.bzl", "junit_tests")
ABSTRACT_QUERY_TEST = ["AbstractQueryGroupsTest.java"]
diff --git a/javatests/com/google/gerrit/server/query/project/BUILD b/javatests/com/google/gerrit/server/query/project/BUILD
index 4ce1c00..e978be6 100644
--- a/javatests/com/google/gerrit/server/query/project/BUILD
+++ b/javatests/com/google/gerrit/server/query/project/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//tools/bzl:junit.bzl", "junit_tests")
ABSTRACT_QUERY_TEST = ["AbstractQueryProjectsTest.java"]
diff --git a/javatests/com/google/gerrit/util/http/testutil/BUILD b/javatests/com/google/gerrit/util/http/testutil/BUILD
index adae68e..5cb94c6 100644
--- a/javatests/com/google/gerrit/util/http/testutil/BUILD
+++ b/javatests/com/google/gerrit/util/http/testutil/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "testutil",
testonly = True,
diff --git a/lib/BUILD b/lib/BUILD
index f98f6fe..ab2bad9 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
exports_files(glob([
"LICENSE-*",
]))
diff --git a/lib/antlr/BUILD b/lib/antlr/BUILD
index c35c2b5..076aea9 100644
--- a/lib/antlr/BUILD
+++ b/lib/antlr/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_binary", "java_library")
+
package(default_visibility = ["//java/com/google/gerrit/index:__pkg__"])
[java_library(
diff --git a/lib/asciidoctor/BUILD b/lib/asciidoctor/BUILD
index 62b1114..b46c08d 100644
--- a/lib/asciidoctor/BUILD
+++ b/lib/asciidoctor/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "asciidoctor",
data = ["//lib:LICENSE-asciidoctor"],
diff --git a/lib/auto/BUILD b/lib/auto/BUILD
index 1e722bc..b60a101 100644
--- a/lib/auto/BUILD
+++ b/lib/auto/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library", "java_plugin")
+
java_plugin(
name = "auto-annotation-plugin",
processor_class = "com.google.auto.value.processor.AutoAnnotationProcessor",
diff --git a/lib/bouncycastle/BUILD b/lib/bouncycastle/BUILD
index cf3e996..43ba6e1 100644
--- a/lib/bouncycastle/BUILD
+++ b/lib/bouncycastle/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "bcprov",
data = ["//lib:LICENSE-bouncycastle"],
diff --git a/lib/commons/BUILD b/lib/commons/BUILD
index e8de396..38b1b6d 100644
--- a/lib/commons/BUILD
+++ b/lib/commons/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
package(default_visibility = ["//visibility:public"])
java_library(
diff --git a/lib/dropwizard/BUILD b/lib/dropwizard/BUILD
index 4ae12f1..174b7ad 100644
--- a/lib/dropwizard/BUILD
+++ b/lib/dropwizard/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "dropwizard-core",
data = ["//lib:LICENSE-Apache2.0"],
diff --git a/lib/easymock/BUILD b/lib/easymock/BUILD
index 352d2a7..90c9673 100644
--- a/lib/easymock/BUILD
+++ b/lib/easymock/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "easymock",
data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
diff --git a/lib/elasticsearch-rest-client/BUILD b/lib/elasticsearch-rest-client/BUILD
index 8df3c70..e323263 100644
--- a/lib/elasticsearch-rest-client/BUILD
+++ b/lib/elasticsearch-rest-client/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
package(default_visibility = ["//visibility:public"])
java_library(
diff --git a/lib/errorprone/BUILD b/lib/errorprone/BUILD
index b5c130b..456860a 100644
--- a/lib/errorprone/BUILD
+++ b/lib/errorprone/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "annotations",
data = ["//lib:LICENSE-Apache2.0"],
diff --git a/lib/flogger/BUILD b/lib/flogger/BUILD
index c41e12f..35c3c62 100644
--- a/lib/flogger/BUILD
+++ b/lib/flogger/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "api",
data = ["//lib:LICENSE-Apache2.0"],
diff --git a/lib/gitiles/BUILD b/lib/gitiles/BUILD
index b1bbca1..6e03801 100644
--- a/lib/gitiles/BUILD
+++ b/lib/gitiles/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "gitiles",
visibility = ["//visibility:public"],
diff --git a/lib/greenmail/BUILD b/lib/greenmail/BUILD
index 9cbd0eb..e8845e2 100644
--- a/lib/greenmail/BUILD
+++ b/lib/greenmail/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
package(default_visibility = ["//visibility:public"])
POST_JDK8_DEPS = [":javax-activation"]
diff --git a/lib/guice/BUILD b/lib/guice/BUILD
index 7f384e2..f73984b 100644
--- a/lib/guice/BUILD
+++ b/lib/guice/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "guice",
data = ["//lib:LICENSE-Apache2.0"],
diff --git a/lib/httpcomponents/BUILD b/lib/httpcomponents/BUILD
index 03d9b68..07d4bb9 100644
--- a/lib/httpcomponents/BUILD
+++ b/lib/httpcomponents/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
package(default_visibility = ["//visibility:public"])
java_library(
diff --git a/lib/jackson/BUILD b/lib/jackson/BUILD
index 0034748..3eed77a 100644
--- a/lib/jackson/BUILD
+++ b/lib/jackson/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "jackson-core",
data = ["//lib:LICENSE-Apache2.0"],
diff --git a/lib/jetty/BUILD b/lib/jetty/BUILD
index b78ac58..6417385 100644
--- a/lib/jetty/BUILD
+++ b/lib/jetty/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "servlet",
data = ["//lib:LICENSE-Apache2.0"],
diff --git a/lib/jgit/jgit.bzl b/lib/jgit/jgit.bzl
index 0f52913..b3d026d 100644
--- a/lib/jgit/jgit.bzl
+++ b/lib/jgit/jgit.bzl
@@ -1,6 +1,6 @@
load("//tools/bzl:maven_jar.bzl", "MAVEN_CENTRAL", "maven_jar")
-_JGIT_VERS = "5.3.1.201904271842-r"
+_JGIT_VERS = "5.4.3.201909031940-r"
_DOC_VERS = _JGIT_VERS # Set to _JGIT_VERS unless using a snapshot
@@ -40,25 +40,25 @@
name = "jgit-lib",
artifact = "org.eclipse.jgit:org.eclipse.jgit:" + _JGIT_VERS,
repository = _JGIT_REPO,
- sha1 = "dba85014483315fa426259bc1b8ccda9373a624b",
+ sha1 = "10322c4e103485f8b4873cbbf982342f9c3d7989",
)
maven_jar(
name = "jgit-servlet",
artifact = "org.eclipse.jgit:org.eclipse.jgit.http.server:" + _JGIT_VERS,
repository = _JGIT_REPO,
- sha1 = "3287341fca859340a00b51cb5dd3b78b8e532b39",
+ sha1 = "59d0c943343f30612e4e2a5a3bf1b95b56e00207",
)
maven_jar(
name = "jgit-archive",
artifact = "org.eclipse.jgit:org.eclipse.jgit.archive:" + _JGIT_VERS,
repository = _JGIT_REPO,
- sha1 = "3585027e83fb44a5de2c10ae9ddbf976593bf080",
+ sha1 = "21dc4a10882dc667c83bf82a563a6fc4d7719456",
)
maven_jar(
name = "jgit-junit",
artifact = "org.eclipse.jgit:org.eclipse.jgit.junit:" + _JGIT_VERS,
repository = _JGIT_REPO,
- sha1 = "3d9ba7e610d6ab5d08dcb1e4ba448b592a34de77",
+ sha1 = "71659fc1a1729b7c67846dac8cd6a762fa72002a",
)
def jgit_dep(name):
diff --git a/lib/jgit/org.eclipse.jgit.archive/BUILD b/lib/jgit/org.eclipse.jgit.archive/BUILD
index 2742623..151cd71 100644
--- a/lib/jgit/org.eclipse.jgit.archive/BUILD
+++ b/lib/jgit/org.eclipse.jgit.archive/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//lib/jgit:jgit.bzl", "jgit_dep")
java_library(
diff --git a/lib/jgit/org.eclipse.jgit.http.server/BUILD b/lib/jgit/org.eclipse.jgit.http.server/BUILD
index 001ad8b..fd634a5 100644
--- a/lib/jgit/org.eclipse.jgit.http.server/BUILD
+++ b/lib/jgit/org.eclipse.jgit.http.server/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//lib/jgit:jgit.bzl", "jgit_dep")
java_library(
diff --git a/lib/jgit/org.eclipse.jgit.junit/BUILD b/lib/jgit/org.eclipse.jgit.junit/BUILD
index 29d80d3..abc522b 100644
--- a/lib/jgit/org.eclipse.jgit.junit/BUILD
+++ b/lib/jgit/org.eclipse.jgit.junit/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//lib/jgit:jgit.bzl", "jgit_dep")
java_library(
diff --git a/lib/jgit/org.eclipse.jgit/BUILD b/lib/jgit/org.eclipse.jgit/BUILD
index dc11171..c1f2607 100644
--- a/lib/jgit/org.eclipse.jgit/BUILD
+++ b/lib/jgit/org.eclipse.jgit/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//lib/jgit:jgit.bzl", "jgit_dep")
java_library(
diff --git a/lib/jsoup/BUILD b/lib/jsoup/BUILD
index 3142dac..7171901 100644
--- a/lib/jsoup/BUILD
+++ b/lib/jsoup/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "jsoup",
data = ["//lib:LICENSE-jsoup"],
diff --git a/lib/log/BUILD b/lib/log/BUILD
index 8e4c927..128e8ba 100644
--- a/lib/log/BUILD
+++ b/lib/log/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "api",
data = ["//lib:LICENSE-slf4j"],
diff --git a/lib/lucene/BUILD b/lib/lucene/BUILD
index adb5030..b8b2457 100644
--- a/lib/lucene/BUILD
+++ b/lib/lucene/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//tools/bzl:maven.bzl", "merge_maven_jars")
package(default_visibility = ["//visibility:public"])
diff --git a/lib/mail/BUILD b/lib/mail/BUILD
index eca2b6b..489f544 100644
--- a/lib/mail/BUILD
+++ b/lib/mail/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "mail",
data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
diff --git a/lib/mime4j/BUILD b/lib/mime4j/BUILD
index ee407c3..577661d 100644
--- a/lib/mime4j/BUILD
+++ b/lib/mime4j/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "core",
data = ["//lib:LICENSE-Apache2.0"],
diff --git a/lib/mina/BUILD b/lib/mina/BUILD
index 6ee7e41..5ad47cd 100644
--- a/lib/mina/BUILD
+++ b/lib/mina/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "sshd",
data = ["//lib:LICENSE-Apache2.0"],
diff --git a/lib/mockito/BUILD b/lib/mockito/BUILD
index fa4839b..7af9669 100644
--- a/lib/mockito/BUILD
+++ b/lib/mockito/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
package(
default_testonly = True,
default_visibility = ["//visibility:private"],
@@ -18,13 +20,13 @@
java_library(
name = "byte-buddy",
data = ["//lib:LICENSE-Apache2.0"],
- exports = ["@byte-buddy//jar"],
+ exports = ["@bytebuddy//jar"],
)
java_library(
name = "byte-buddy-agent",
data = ["//lib:LICENSE-Apache2.0"],
- exports = ["@byte-buddy-agent//jar"],
+ exports = ["@bytebuddy-agent//jar"],
)
java_library(
diff --git a/lib/openid/BUILD b/lib/openid/BUILD
index faa073b..c27e8ab 100644
--- a/lib/openid/BUILD
+++ b/lib/openid/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "consumer",
data = ["//lib:LICENSE-Apache2.0"],
diff --git a/lib/ow2/BUILD b/lib/ow2/BUILD
index 5a82572..7fe7e2d 100644
--- a/lib/ow2/BUILD
+++ b/lib/ow2/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "ow2-asm",
data = ["//lib:LICENSE-ow2"],
diff --git a/lib/powermock/BUILD b/lib/powermock/BUILD
index 57880f4..39df164 100644
--- a/lib/powermock/BUILD
+++ b/lib/powermock/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "powermock-module-junit4",
data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
diff --git a/lib/prolog/BUILD b/lib/prolog/BUILD
index 8518af7..fa55682 100644
--- a/lib/prolog/BUILD
+++ b/lib/prolog/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_binary", "java_library")
+
java_library(
name = "runtime",
data = ["//lib:LICENSE-prologcafe"],
@@ -42,14 +44,14 @@
java_binary(
name = "compiler-bin",
- main_class = "BuckPrologCompiler",
+ main_class = "BazelPrologCompiler",
visibility = ["//visibility:public"],
runtime_deps = [":compiler-lib"],
)
java_library(
name = "compiler-lib",
- srcs = ["java/BuckPrologCompiler.java"],
+ srcs = ["java/BazelPrologCompiler.java"],
visibility = ["//visibility:public"],
deps = [
":compiler",
diff --git a/lib/prolog/java/BuckPrologCompiler.java b/lib/prolog/java/BazelPrologCompiler.java
similarity index 98%
rename from lib/prolog/java/BuckPrologCompiler.java
rename to lib/prolog/java/BazelPrologCompiler.java
index cc3e39e..37ea696 100644
--- a/lib/prolog/java/BuckPrologCompiler.java
+++ b/lib/prolog/java/BazelPrologCompiler.java
@@ -21,7 +21,7 @@
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
-public class BuckPrologCompiler {
+public class BazelPrologCompiler {
private static File tmpdir;
public static void main(String[] argv) throws IOException, CompileException {
diff --git a/lib/prolog/prolog.bzl b/lib/prolog/prolog.bzl
index 4d4dd3a..ffc3198 100644
--- a/lib/prolog/prolog.bzl
+++ b/lib/prolog/prolog.bzl
@@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+load("@rules_java//java:defs.bzl", "java_library")
+
def prolog_cafe_library(
name,
srcs,
@@ -26,7 +28,7 @@
tools = ["//lib/prolog:compiler-bin"],
outs = [name + ".srcjar"],
)
- native.java_library(
+ java_library(
name = name,
srcs = [":" + name + "__pl2j"],
deps = ["//lib/prolog:runtime-neverlink"] + deps,
diff --git a/lib/testcontainers/BUILD b/lib/testcontainers/BUILD
index 25ca327..a37b733 100644
--- a/lib/testcontainers/BUILD
+++ b/lib/testcontainers/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "duct-tape",
testonly = True,
diff --git a/lib/truth/BUILD b/lib/truth/BUILD
index db5bc48..bb30945 100644
--- a/lib/truth/BUILD
+++ b/lib/truth/BUILD
@@ -1,3 +1,5 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
java_library(
name = "truth",
data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
diff --git a/plugins/BUILD b/plugins/BUILD
index 7d81213..3e93768 100644
--- a/plugins/BUILD
+++ b/plugins/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_binary", "java_library")
load("//tools/bzl:genrule2.bzl", "genrule2")
load("//tools/bzl:javadoc.bzl", "java_doc")
load(
diff --git a/plugins/delete-project b/plugins/delete-project
index b618043..3a4b095 160000
--- a/plugins/delete-project
+++ b/plugins/delete-project
@@ -1 +1 @@
-Subproject commit b618043544ebc62a0730aa1bfc1a1e26011b471a
+Subproject commit 3a4b0955529588dbc071bc43164c468b29d79129
diff --git a/plugins/gitiles b/plugins/gitiles
index 3764262..bdbed9a 160000
--- a/plugins/gitiles
+++ b/plugins/gitiles
@@ -1 +1 @@
-Subproject commit 37642627f0a4e6a3b2990ec754120b5055de6d41
+Subproject commit bdbed9af9bb2b77cd7fc8681da2dcee7e8f30264
diff --git a/plugins/plugin-manager b/plugins/plugin-manager
index 833889d..69343c6 160000
--- a/plugins/plugin-manager
+++ b/plugins/plugin-manager
@@ -1 +1 @@
-Subproject commit 833889d327a159b5ccea7064f4fcff3f94d4b26e
+Subproject commit 69343c65a66d752c3a41788c191a38fc64cc2a32
diff --git a/plugins/replication b/plugins/replication
index a3ca5f8..485027c 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit a3ca5f86bb58852b106e3fdb91a79a6cc11bf312
+Subproject commit 485027c2d63dda7b588fa5c07f91d2a65db4d787
diff --git a/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior.html b/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior.html
index db11937..182d242 100644
--- a/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior.html
@@ -18,8 +18,6 @@
(function(window) {
'use strict';
- const ACCOUNT_CAPABILITIES = ['createProject', 'createGroup', 'viewPlugins'];
-
const ADMIN_LINKS = [{
name: 'Repositories',
noBaseUrl: true,
@@ -65,7 +63,7 @@
return Promise.resolve(this._filterLinks(link => link.viewableToAll,
getAdminMenuLinks, opt_options));
}
- return getAccountCapabilities(ACCOUNT_CAPABILITIES)
+ return getAccountCapabilities()
.then(capabilities => {
return this._filterLinks(link => {
return !link.capability ||
@@ -97,9 +95,10 @@
links.push(...getAdminMenuLinks().map(link => ({
url: link.url,
name: link.text,
+ capability: link.capability || null,
noBaseUrl: !isExernalLink(link),
view: null,
- viewableToAll: true,
+ viewableToAll: !link.capability,
target: isExernalLink(link) ? '_blank' : null,
})));
diff --git a/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior_test.html b/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior_test.html
index 60817aa..f1e28d1 100644
--- a/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior_test.html
@@ -310,5 +310,61 @@
testAdminLinks(account, options, expected, done);
});
});
+
+
+ suite('view plugin screen with plugin capability', () => {
+ const account = {
+ name: 'test-user',
+ };
+ let expected;
+
+ setup(() => {
+ capabilityStub.returns(Promise.resolve({pluginCapability: true}));
+ expected = {};
+ });
+
+ test('with plugin with capabilities', done => {
+ let options;
+ const generatedLinks = [
+ {text: 'without capability', url: '/without'},
+ {text: 'with capability', url: '/with', capability: 'pluginCapability'},
+ ];
+ menuLinkStub.returns(generatedLinks);
+ expected = Object.assign(expected, {
+ totalLength: 4,
+ pluginGeneratedLinks: generatedLinks,
+ });
+ testAdminLinks(account, options, expected, done);
+ });
+ });
+
+
+ suite('view plugin screen without plugin capability', () => {
+ const account = {
+ name: 'test-user',
+ };
+ let expected;
+
+ setup(() => {
+ capabilityStub.returns(Promise.resolve({}));
+ expected = {};
+ });
+
+ test('with plugin with capabilities', done => {
+ let options;
+ const generatedLinks = [
+ {text: 'without capability', url: '/without'},
+ {text: 'with capability',
+ url: '/with',
+ capability: 'pluginCapability'},
+ ];
+ menuLinkStub.returns(generatedLinks);
+ expected = Object.assign(expected, {
+ totalLength: 3,
+ pluginGeneratedLinks: [generatedLinks[0]],
+ });
+ testAdminLinks(account, options, expected, done);
+ });
+ });
});
</script>
diff --git a/polygerrit-ui/app/behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html b/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior.html
similarity index 60%
rename from polygerrit-ui/app/behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html
rename to polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior.html
index 40379e4..3106fc8 100644
--- a/polygerrit-ui/app/behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior.html
@@ -15,33 +15,28 @@
limitations under the License.
-->
+<script src="../../scripts/gr-display-name-utils/gr-display-name-utils.js"></script>
+
<script>
(function(window) {
'use strict';
- const ANONYMOUS_NAME = 'Anonymous';
-
window.Gerrit = window.Gerrit || {};
- /** @polymerBehavior Gerrit.AnonymousNameBehavior */
- Gerrit.AnonymousNameBehavior = {
+ /** @polymerBehavior Gerrit.DisplayNameBehavior */
+ Gerrit.DisplayNameBehavior = {
+ // TODO(dmfilippov) replace DisplayNameBehavior with GrDisplayNameUtils
+
/**
* enableEmail when true enables to fallback to using email if
* the account name is not avilable.
*/
getUserName(config, account, enableEmail) {
- if (account && account.name) {
- return account.name;
- } else if (account && account.username) {
- return account.username;
- } else if (enableEmail && account && account.email) {
- return account.email;
- } else if (config && config.user &&
- config.user.anonymous_coward_name !== 'Anonymous Coward') {
- return config.user.anonymous_coward_name;
- }
+ return GrDisplayNameUtils.getUserName(config, account, enableEmail);
+ },
- return ANONYMOUS_NAME;
+ getGroupDisplayName(group) {
+ return GrDisplayNameUtils.getGroupDisplayName(group);
},
};
})(window);
diff --git a/polygerrit-ui/app/behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior_test.html b/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior_test.html
similarity index 79%
rename from polygerrit-ui/app/behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior_test.html
rename to polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior_test.html
index 64f0b3a..4c5c899 100644
--- a/polygerrit-ui/app/behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior_test.html
@@ -17,14 +17,14 @@
-->
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
-<title>gr-anonymous-name-behavior</title>
+<title>gr-display-name-behavior</title>
<script src="/test/common-test-setup.js"></script>
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../test/common-test-setup.html"/>
-<link rel="import" href="gr-anonymous-name-behavior.html">
+<link rel="import" href="gr-display-name-behavior.html">
<test-fixture id="basic">
<template>
@@ -33,7 +33,7 @@
</test-fixture>
<script>
- suite('gr-anonymous-name-behavior tests', () => {
+ suite('gr-display-name-behavior tests', () => {
let element;
// eslint-disable-next-line no-unused-vars
const config = {
@@ -48,7 +48,7 @@
is: 'test-element-anon',
_legacyUndefinedCheck: true,
behaviors: [
- Gerrit.AnonymousNameBehavior,
+ Gerrit.DisplayNameBehavior,
],
});
});
@@ -57,21 +57,21 @@
element = fixture('basic');
});
- test('test for it to return name', () => {
+ test('getUserName name only', () => {
const account = {
name: 'test-name',
};
assert.deepEqual(element.getUserName(config, account, true), 'test-name');
});
- test('test for it to return username', () => {
+ test('getUserName username only', () => {
const account = {
username: 'test-user',
};
assert.deepEqual(element.getUserName(config, account, true), 'test-user');
});
- test('test for it to return email', () => {
+ test('getUserName email only', () => {
const account = {
email: 'test-user@test-url.com',
};
@@ -79,11 +79,11 @@
'test-user@test-url.com');
});
- test('test for it not to Anonymous Coward as the anon name', () => {
+ test('getUserName returns not Anonymous Coward as the anon name', () => {
assert.deepEqual(element.getUserName(config, null, true), 'Anonymous');
});
- test('test for the config returning the anon name', () => {
+ test('getUserName for the config returning the anon name', () => {
const config = {
user: {
anonymous_coward_name: 'Test Anon',
@@ -91,5 +91,10 @@
};
assert.deepEqual(element.getUserName(config, null, true), 'Test Anon');
});
+
+ test('getGroupDisplayName', () => {
+ assert.equal(element.getGroupDisplayName({name: 'Some user name'}),
+ 'Some user name (group)');
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html
index 178d056..984be19 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html
@@ -160,6 +160,7 @@
return element.reload().then(() => {
assert.equal(element._filteredLinks.length, 3);
assert.deepEqual(element._filteredLinks[1], {
+ capability: null,
url: '/internal/link/url',
name: 'internal link text',
noBaseUrl: true,
@@ -168,6 +169,7 @@
target: null,
});
assert.deepEqual(element._filteredLinks[2], {
+ capability: null,
url: 'http://external/link/url',
name: 'external link text',
noBaseUrl: false,
diff --git a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js
deleted file mode 100644
index 1cb1ca5..0000000
--- a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js
+++ /dev/null
@@ -1,186 +0,0 @@
-/**
- * @license
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-(function() {
- 'use strict';
-
- Polymer({
- is: 'gr-account-entry',
- _legacyUndefinedCheck: true,
-
- /**
- * Fired when an account is entered.
- *
- * @event add
- */
-
- /**
- * When allowAnyInput is true, account-text-changed is fired when input text
- * changed. This is needed so that the reply dialog's save button can be
- * enabled for arbitrary cc's, which don't need a 'commit'.
- *
- * @event account-text-changed
- */
- properties: {
- allowAnyInput: Boolean,
- borderless: Boolean,
- change: Object,
- filter: Function,
- placeholder: String,
- /**
- * When true, account-entry uses the account suggest API endpoint, which
- * suggests any account in that Gerrit instance (and does not suggest
- * groups).
- *
- * When false/undefined, account-entry uses the suggest_reviewers API
- * endpoint, which suggests any account or group in that Gerrit instance
- * that is not already a reviewer (or is not CCed) on that change.
- */
- allowAnyUser: Boolean,
-
- // suggestFrom = 0 to enable default suggestions.
- suggestFrom: {
- type: Number,
- value: 0,
- },
-
- query: {
- type: Function,
- value() {
- return this._getReviewerSuggestions.bind(this);
- },
- },
-
- _config: Object,
- /** The value of the autocomplete entry. */
- _inputText: {
- type: String,
- observer: '_inputTextChanged',
- },
-
- _loggedIn: Boolean,
- },
-
- behaviors: [
- Gerrit.AnonymousNameBehavior,
- Gerrit.FireBehavior,
- ],
-
- attached() {
- this.$.restAPI.getConfig().then(cfg => {
- this._config = cfg;
- });
- this.$.restAPI.getLoggedIn().then(loggedIn => {
- this._loggedIn = loggedIn;
- });
- },
-
- get focusStart() {
- return this.$.input.focusStart;
- },
-
- focus() {
- this.$.input.focus();
- },
-
- clear() {
- this.$.input.clear();
- },
-
- setText(text) {
- this.$.input.setText(text);
- },
-
- getText() {
- return this.$.input.text;
- },
-
- _handleInputCommit(e) {
- this.fire('add', {value: e.detail.value});
- this.$.input.focus();
- },
-
- _accountOrAnon(reviewer) {
- return this.getUserName(this._config, reviewer, false);
- },
-
- _inputTextChanged(text) {
- if (text.length && this.allowAnyInput) {
- this.dispatchEvent(new CustomEvent(
- 'account-text-changed', {bubbles: true, composed: true}));
- }
- },
-
- _makeSuggestion(reviewer) {
- let name;
- let value;
- const generateStatusStr = function(account) {
- return account.status ? '(' + account.status + ')' : '';
- };
- if (reviewer.account) {
- // Reviewer is an account suggestion from getChangeSuggestedReviewers.
- const reviewerName = this._accountOrAnon(reviewer.account);
- const reviewerEmail = this._reviewerEmail(reviewer.account.email);
- const reviewerStatus = generateStatusStr(reviewer.account);
- name = [reviewerName, reviewerEmail, reviewerStatus]
- .filter(p => p.length > 0).join(' ');
- value = reviewer;
- } else if (reviewer.group) {
- // Reviewer is a group suggestion from getChangeSuggestedReviewers.
- name = reviewer.group.name + ' (group)';
- value = reviewer;
- } else if (reviewer._account_id) {
- // Reviewer is an account suggestion from getSuggestedAccounts.
- const reviewerName = this._accountOrAnon(reviewer);
- const reviewerEmail = this._reviewerEmail(reviewer.email);
- const reviewerStatus = generateStatusStr(reviewer);
- name = [reviewerName, reviewerEmail, reviewerStatus]
- .filter(p => p.length > 0).join(' ');
- value = {account: reviewer, count: 1};
- }
- return {name, value};
- },
-
- _getReviewerSuggestions(input) {
- if (!this.change || !this.change._number || !this._loggedIn) {
- return Promise.resolve([]);
- }
-
- const api = this.$.restAPI;
- const xhr = this.allowAnyUser ?
- api.getSuggestedAccounts(`cansee:${this.change._number} ${input}`) :
- api.getChangeSuggestedReviewers(this.change._number, input);
-
- return xhr.then(reviewers => {
- if (!reviewers) { return []; }
- if (!this.filter) {
- return reviewers.map(this._makeSuggestion.bind(this));
- }
- return reviewers
- .filter(this.filter)
- .map(this._makeSuggestion.bind(this));
- });
- },
-
- _reviewerEmail(email) {
- if (typeof email !== 'undefined') {
- return '<' + email + '>';
- }
-
- return '';
- },
- });
-})();
diff --git a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html
deleted file mode 100644
index 57bdd1d..0000000
--- a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html
+++ /dev/null
@@ -1,276 +0,0 @@
-<!DOCTYPE html>
-<!--
-@license
-Copyright (C) 2016 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
--->
-
-<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
-<title>gr-account-entry</title>
-<script src="/test/common-test-setup.js"></script>
-<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-
-<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
-<script src="/bower_components/web-component-tester/browser.js"></script>
-<link rel="import" href="../../../test/common-test-setup.html"/>
-<script src="../../../scripts/util.js"></script>
-
-<link rel="import" href="gr-account-entry.html">
-
-<script>void(0);</script>
-
-<test-fixture id="basic">
- <template>
- <gr-account-entry></gr-account-entry>
- </template>
-</test-fixture>
-
-<script>
- suite('gr-account-entry tests', () => {
- let sandbox;
- let _nextAccountId = 0;
- const makeAccount = function(opt_status) {
- const accountId = ++_nextAccountId;
- return {
- _account_id: accountId,
- name: 'name ' + accountId,
- email: 'email ' + accountId,
- status: opt_status,
- };
- };
- let _nextAccountId2 = 0;
- const makeAccount2 = function(opt_status) {
- const accountId2 = ++_nextAccountId2;
- return {
- _account_id: accountId2,
- email: 'email ' + accountId2,
- status: opt_status,
- };
- };
- let _nextAccountId3 = 0;
- const makeAccount3 = function(opt_status) {
- const accountId3 = ++_nextAccountId3;
- return {
- _account_id: accountId3,
- name: 'name ' + accountId3,
- status: opt_status,
- };
- };
-
- let owner;
- let existingReviewer1;
- let existingReviewer2;
- let suggestion1;
- let suggestion2;
- let suggestion3;
- let element;
-
- setup(done => {
- owner = makeAccount();
- existingReviewer1 = makeAccount();
- existingReviewer2 = makeAccount();
- suggestion1 = {account: makeAccount()};
- suggestion2 = {account: makeAccount()};
- suggestion3 = {
- group: {
- id: 'suggested group id',
- name: 'suggested group',
- },
- };
-
- stub('gr-rest-api-interface', {
- getLoggedIn() { return Promise.resolve(true); },
- });
-
- element = fixture('basic');
- element.change = {
- _number: 42,
- owner,
- reviewers: {
- CC: [existingReviewer1],
- REVIEWER: [existingReviewer2],
- },
- };
- sandbox = sinon.sandbox.create();
- return flush(done);
- });
-
- teardown(() => {
- sandbox.restore();
- });
-
- suite('stubbed values for _getReviewerSuggestions', () => {
- setup(() => {
- stub('gr-rest-api-interface', {
- getChangeSuggestedReviewers() {
- const redundantSuggestion1 = {account: existingReviewer1};
- const redundantSuggestion2 = {account: existingReviewer2};
- const redundantSuggestion3 = {account: owner};
- return Promise.resolve([redundantSuggestion1, redundantSuggestion2,
- redundantSuggestion3, suggestion1, suggestion2, suggestion3]);
- },
- });
- });
-
- test('_makeSuggestion formats account or group accordingly', () => {
- let account = makeAccount();
- const account2 = makeAccount2();
- const account3 = makeAccount3();
- let suggestion = element._makeSuggestion({account});
- assert.deepEqual(suggestion, {
- name: account.name + ' <' + account.email + '>',
- value: {account},
- });
-
- const group = {name: 'test'};
- suggestion = element._makeSuggestion({group});
- assert.deepEqual(suggestion, {
- name: group.name + ' (group)',
- value: {group},
- });
-
- suggestion = element._makeSuggestion(account);
- assert.deepEqual(suggestion, {
- name: account.name + ' <' + account.email + '>',
- value: {account, count: 1},
- });
-
- element._config = {
- user: {
- anonymous_coward_name: 'Anonymous Coward',
- },
- };
- assert.deepEqual(element._accountOrAnon(account2), 'Anonymous');
-
- account = makeAccount('OOO');
-
- suggestion = element._makeSuggestion({account});
- assert.deepEqual(suggestion, {
- name: account.name + ' <' + account.email + '> (OOO)',
- value: {account},
- });
-
- suggestion = element._makeSuggestion(account);
- assert.deepEqual(suggestion, {
- name: account.name + ' <' + account.email + '> (OOO)',
- value: {account, count: 1},
- });
-
- sandbox.stub(element, '_reviewerEmail',
- () => { return ''; });
-
- suggestion = element._makeSuggestion(account3);
- assert.deepEqual(suggestion, {
- name: account3.name,
- value: {account: account3, count: 1},
- });
- });
-
- test('_reviewerEmail', () => {
- assert.equal(
- element._reviewerEmail('email@gerritreview.com'),
- '<email@gerritreview.com>');
- assert.equal(element._reviewerEmail(undefined), '');
- });
-
- test('_getReviewerSuggestions excludes owner+reviewers', done => {
- element._getReviewerSuggestions().then(reviewers => {
- // Default is no filtering.
- assert.equal(reviewers.length, 6);
-
- // Set up filter that only accepts suggestion1.
- const accountId = suggestion1.account._account_id;
- element.filter = function(suggestion) {
- return suggestion.account &&
- suggestion.account._account_id === accountId;
- };
-
- element._getReviewerSuggestions().then(reviewers => {
- assert.deepEqual(reviewers, [element._makeSuggestion(suggestion1)]);
- }).then(done);
- });
- });
-
- test('_getReviewerSuggestions short circuits when logged out', () => {
- // API call is already stubbed.
- const xhrSpy = element.$.restAPI.getChangeSuggestedReviewers;
- element._loggedIn = false;
- return element._getReviewerSuggestions('').then(() => {
- assert.isFalse(xhrSpy.called);
- element._loggedIn = true;
- return element._getReviewerSuggestions('').then(() => {
- assert.isTrue(xhrSpy.called);
- });
- });
- });
- });
-
- test('allowAnyUser', done => {
- const suggestReviewerStub =
- sandbox.stub(element.$.restAPI, 'getChangeSuggestedReviewers')
- .returns(Promise.resolve([]));
- const suggestAccountStub =
- sandbox.stub(element.$.restAPI, 'getSuggestedAccounts')
- .returns(Promise.resolve([]));
-
- element._getReviewerSuggestions('').then(() => {
- assert.isTrue(suggestReviewerStub.calledOnce);
- assert.isTrue(suggestReviewerStub.calledWith(42, ''));
- assert.isFalse(suggestAccountStub.called);
- element.allowAnyUser = true;
-
- element._getReviewerSuggestions('').then(() => {
- assert.isTrue(suggestReviewerStub.calledOnce);
- assert.isTrue(suggestAccountStub.calledOnce);
- assert.isTrue(suggestAccountStub.calledWith('cansee:42 '));
- done();
- });
- });
- });
- test('account-text-changed fired when input text changed and allowAnyInput',
- () => {
- // Spy on query, as that is called when _updateSuggestions proceeds.
- const changeStub = sandbox.stub();
- element.allowAnyInput = true;
- sandbox.stub(element.$.restAPI, 'getChangeSuggestedReviewers')
- .returns(Promise.resolve([]));
- element.addEventListener('account-text-changed', changeStub);
- element.$.input.text = 'a';
- assert.isTrue(changeStub.calledOnce);
- element.$.input.text = 'ab';
- assert.isTrue(changeStub.calledTwice);
- });
-
- test('account-text-changed not fired when input text changed without ' +
- 'allowAnyUser', () => {
- // Spy on query, as that is called when _updateSuggestions proceeds.
- const changeStub = sandbox.stub();
- sandbox.stub(element.$.restAPI, 'getChangeSuggestedReviewers')
- .returns(Promise.resolve([]));
- element.addEventListener('account-text-changed', changeStub);
- element.$.input.text = 'a';
- assert.isFalse(changeStub.called);
- });
-
- test('setText', () => {
- // Spy on query, as that is called when _updateSuggestions proceeds.
- const suggestSpy = sandbox.spy(element.$.input, 'query');
- element.setText('test text');
- flushAsynchronousOperations();
-
- assert.equal(element.$.input.$.input.value, 'test text');
- assert.isFalse(suggestSpy.called);
- });
- });
-</script>
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
index 15892c9..ee35583 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
@@ -36,6 +36,8 @@
<link rel="import" href="../gr-change-requirements/gr-change-requirements.html">
<link rel="import" href="../gr-commit-info/gr-commit-info.html">
<link rel="import" href="../gr-reviewer-list/gr-reviewer-list.html">
+<script src="../../../scripts/gr-display-name-utils/gr-display-name-utils.js"></script>
+<script src="../../../scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.js"></script>
<dom-module id="gr-change-metadata">
<template>
@@ -172,9 +174,9 @@
id="assigneeValue"
placeholder="Set assignee..."
accounts="{{_assignee}}"
- change="[[change]]"
readonly="[[_computeAssigneeReadOnly(_mutable, change)]]"
- allow-any-user></gr-account-list>
+ suggestions-provider="[[_getReviewerSuggestionsProvider(change)]]">
+ </gr-account-list>
</span>
</section>
<section>
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
index 6ee5a24..62446e4 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
@@ -471,5 +471,12 @@
// dom-if.
this.$$('.topicEditableLabel').open();
},
+
+ _getReviewerSuggestionsProvider(change) {
+ const provider = new GrReviewerSuggestionsProvider(this.$.restAPI,
+ change._number, true);
+ provider.init();
+ return provider;
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
index 08a3ec3..38c4a96 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
@@ -32,9 +32,11 @@
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../shared/gr-storage/gr-storage.html">
-<link rel="import" href="../gr-account-list/gr-account-list.html">
+<link rel="import" href="../../shared/gr-account-list/gr-account-list.html">
<link rel="import" href="../gr-label-scores/gr-label-scores.html">
<link rel="import" href="../../../styles/shared-styles.html">
+<script src="../../../scripts/gr-display-name-utils/gr-display-name-utils.js"></script>
+<script src="../../../scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.js"></script>
<dom-module id="gr-reply-dialog">
<template>
@@ -165,11 +167,11 @@
id="reviewers"
accounts="{{_reviewers}}"
removable-values="[[change.removable_reviewers]]"
- change="[[change]]"
filter="[[filterReviewerSuggestion]]"
pending-confirmation="{{_reviewerPendingConfirmation}}"
placeholder="Add reviewer..."
- on-account-text-changed="_handleAccountTextEntry">
+ on-account-text-changed="_handleAccountTextEntry"
+ suggestions-provider="[[_getReviewerSuggestionsProvider(change)]]">
</gr-account-list>
</div>
<div class="peopleList">
@@ -177,12 +179,12 @@
<gr-account-list
id="ccs"
accounts="{{_ccs}}"
- change="[[change]]"
filter="[[filterCCSuggestion]]"
pending-confirmation="{{_ccPendingConfirmation}}"
allow-any-input
placeholder="Add CC..."
- on-account-text-changed="_handleAccountTextEntry">
+ on-account-text-changed="_handleAccountTextEntry"
+ suggestions-provider="[[_getReviewerSuggestionsProvider(change)]]">
</gr-account-list>
</div>
<gr-overlay
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index 3b97439..6b7359f 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -895,5 +895,12 @@
_sendDisabledChanged(sendDisabled) {
this.dispatchEvent(new CustomEvent('send-disabled-changed'));
},
+
+ _getReviewerSuggestionsProvider(change) {
+ const provider = new GrReviewerSuggestionsProvider(this.$.restAPI,
+ change._number, false);
+ provider.init();
+ return provider;
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.html b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.html
index 7949002..dc18f8a 100644
--- a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.html
+++ b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.html
@@ -15,7 +15,7 @@
limitations under the License.
-->
-<link rel="import" href="../../../behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html">
+<link rel="import" href="../../../behaviors/gr-display-name-behavior/gr-display-name-behavior.html">
<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-dropdown/gr-dropdown.html">
diff --git a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js
index 2ade782..f8288c6 100644
--- a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js
+++ b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js
@@ -58,7 +58,7 @@
},
behaviors: [
- Gerrit.AnonymousNameBehavior,
+ Gerrit.DisplayNameBehavior,
],
detached() {
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
index 397189a..2bd4a620 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
@@ -109,6 +109,9 @@
const pending = [];
+ const loadedPlugins = [];
+ const detectedExtensions = [];
+
const onError = function(oldOnError, msg, url, line, column, error) {
if (oldOnError) {
oldOnError(msg, url, line, column, error);
@@ -190,6 +193,7 @@
reporter(...args) {
const report = (this._isMetricsPluginLoaded() && !pending.length) ?
this.defaultReporter : this.cachingReporter;
+ args.splice(4, 0, loadedPlugins, detectedExtensions);
report.apply(this, args);
},
@@ -199,16 +203,23 @@
* @param {string} category
* @param {string} eventName
* @param {string|number} eventValue
+ * @param {Array} plugins
+ * @param {Array} extensions
* @param {boolean|undefined} opt_noLog If true, the event will not be
* logged to the JS console.
*/
- defaultReporter(type, category, eventName, eventValue, opt_noLog) {
+ defaultReporter(type, category, eventName, eventValue,
+ loadedPlugins, detectedExtensions, opt_noLog) {
const detail = {
type,
category,
name: eventName,
value: eventValue,
};
+ if (category === TIMING.CATEGORY_UI_LATENCY) {
+ detail.loadedPlugins = loadedPlugins;
+ detail.detectedExtensions = detectedExtensions;
+ }
document.dispatchEvent(new CustomEvent(type, {detail}));
if (opt_noLog) { return; }
if (type === ERROR.TYPE && category === ERROR.CATEGORY) {
@@ -229,10 +240,13 @@
* @param {string} category
* @param {string} eventName
* @param {string|number} eventValue
+ * @param {Array} plugins
+ * @param {Array} extensions
* @param {boolean|undefined} opt_noLog If true, the event will not be
* logged to the JS console.
*/
- cachingReporter(type, category, eventName, eventValue, opt_noLog) {
+ cachingReporter(type, category, eventName, eventValue,
+ plugins, extensions, opt_noLog) {
if (type === ERROR.TYPE && category === ERROR.CATEGORY) {
console.error(eventValue && eventValue.error || eventName);
}
@@ -242,9 +256,11 @@
this.reporter(...args);
}
}
- this.reporter(type, category, eventName, eventValue, opt_noLog);
+ this.reporter(type, category, eventName, eventValue,
+ plugins, extensions, opt_noLog);
} else {
- pending.push([type, category, eventName, eventValue, opt_noLog]);
+ pending.push([type, category, eventName, eventValue,
+ plugins, extensions, opt_noLog]);
}
},
@@ -337,12 +353,16 @@
reportExtension(name) {
this.reporter(EXTENSION.TYPE, EXTENSION.DETECTED, name);
+ if (!detectedExtensions.includes(name)) {
+ detectedExtensions.push(name);
+ }
},
pluginLoaded(name) {
if (name.startsWith('metrics-')) {
this.timeEnd(TIMER.METRICS_PLUGIN_LOADED);
}
+ loadedPlugins.push(name);
},
pluginsLoaded(pluginsList) {
@@ -487,7 +507,7 @@
reportErrorDialog(message) {
this.reporter(ERROR_DIALOG.TYPE, ERROR_DIALOG.CATEGORY,
- 'ErrorDialog: ' + message);
+ 'ErrorDialog: ' + message, {error: new Error(message)});
},
});
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
index a0a25b3..805cef0 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
@@ -265,7 +265,7 @@
test('pluginsLoaded reports time', () => {
sandbox.stub(element, 'now').returns(42);
element.pluginsLoaded();
- assert.isTrue(element.defaultReporter.calledWithExactly(
+ assert.isTrue(element.defaultReporter.calledWith(
'timing-report', 'UI Latency', 'PluginsLoaded', 42
));
});
@@ -287,6 +287,19 @@
assert.isTrue(element.defaultReporter.called);
});
+ test('reports plugins in timing events', () => {
+ element.pluginsLoaded = [];
+ sandbox.stub(element, 'now').returns(42);
+ element.pluginLoaded('metrics-xyz1');
+ // element.pluginLoaded('foo');
+ element.time('timeAction');
+ element.timeEnd('timeAction');
+ assert.isTrue(element.defaultReporter.getCall(1).calledWith(
+ 'timing-report', 'UI Latency', 'timeAction', 0,
+ ['metrics-xyz1']
+ ));
+ });
+
test('reports if metrics plugin xyz is loaded', () => {
element.pluginLoaded('metrics-xyz');
assert.isTrue(element.defaultReporter.called);
diff --git a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.html b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.html
index 06e354c..c4ae41b 100644
--- a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.html
+++ b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.html
@@ -16,7 +16,7 @@
-->
<link rel="import" href="/bower_components/polymer/polymer.html">
-<link rel="import" href="../../../behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html">
+<link rel="import" href="../../../behaviors/gr-display-name-behavior/gr-display-name-behavior.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-search-bar/gr-search-bar.html">
diff --git a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.js b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.js
index fed02d6..017310d 100644
--- a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.js
+++ b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.js
@@ -49,7 +49,7 @@
},
behaviors: [
- Gerrit.AnonymousNameBehavior,
+ Gerrit.DisplayNameBehavior,
],
attached() {
diff --git a/polygerrit-ui/app/elements/gr-app-it_test.html b/polygerrit-ui/app/elements/custom-dark-theme_test.html
similarity index 79%
copy from polygerrit-ui/app/elements/gr-app-it_test.html
copy to polygerrit-ui/app/elements/custom-dark-theme_test.html
index d62c082..4cf35f1 100644
--- a/polygerrit-ui/app/elements/gr-app-it_test.html
+++ b/polygerrit-ui/app/elements/custom-dark-theme_test.html
@@ -24,7 +24,7 @@
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../test/common-test-setup.html"/>
-<link rel="import" href="gr-app.html">
+<link rel="import" href="./gr-app.html">
<script>void(0);</script>
@@ -35,7 +35,7 @@
</test-fixture>
<script>
- suite('gr-app integration tests', () => {
+ suite('gr-app custom dark theme tests', () => {
let sandbox;
let element;
@@ -63,13 +63,22 @@
getVersion() { return Promise.resolve(42); },
getLoggedIn() { return Promise.resolve(false); },
});
+
+ window.localStorage.setItem('dark-theme', 'true');
+
element = fixture('element');
- const importSpy = sandbox.spy(element.$.externalStyle, '_import');
+ const importSpy = sandbox.spy(
+ element.$['app-element'].$.externalStyleForAll,
+ '_import');
+ const importForThemeSpy = sandbox.spy(
+ element.$['app-element'].$.externalStyleForTheme,
+ '_import');
Gerrit.awaitPluginsLoaded().then(() => {
- Promise.all(importSpy.returnValues).then(() => {
- flush(done);
- });
+ Promise.all(importSpy.returnValues.concat(importForThemeSpy.returnValues))
+ .then(() => {
+ flush(done);
+ });
});
});
@@ -77,21 +86,16 @@
sandbox.restore();
});
- test('applies --primary-text-color', () => {
+ test('applies the right theme', () => {
assert.equal(
util.getComputedStyleValue('--primary-text-color', element),
- '#F00BAA');
- });
-
- test('applies --header-background-color', () => {
+ 'red');
assert.equal(
util.getComputedStyleValue('--header-background-color', element),
- '#F01BAA');
- });
- test('applies --footer-background-color', () => {
+ 'black');
assert.equal(
util.getComputedStyleValue('--footer-background-color', element),
- '#F02BAA');
+ 'yellow');
});
});
</script>
diff --git a/polygerrit-ui/app/elements/gr-app-it_test.html b/polygerrit-ui/app/elements/custom-light-theme_test.html
similarity index 82%
rename from polygerrit-ui/app/elements/gr-app-it_test.html
rename to polygerrit-ui/app/elements/custom-light-theme_test.html
index d62c082..e346af5 100644
--- a/polygerrit-ui/app/elements/gr-app-it_test.html
+++ b/polygerrit-ui/app/elements/custom-light-theme_test.html
@@ -35,7 +35,7 @@
</test-fixture>
<script>
- suite('gr-app integration tests', () => {
+ suite('gr-app custom light theme tests', () => {
let sandbox;
let element;
@@ -63,13 +63,22 @@
getVersion() { return Promise.resolve(42); },
getLoggedIn() { return Promise.resolve(false); },
});
+
+ window.localStorage.removeItem('dark-theme');
+
element = fixture('element');
- const importSpy = sandbox.spy(element.$.externalStyle, '_import');
+ const importSpy = sandbox.spy(
+ element.$['app-element'].$.externalStyleForAll,
+ '_import');
+ const importForThemeSpy = sandbox.spy(
+ element.$['app-element'].$.externalStyleForTheme,
+ '_import');
Gerrit.awaitPluginsLoaded().then(() => {
- Promise.all(importSpy.returnValues).then(() => {
- flush(done);
- });
+ Promise.all(importSpy.returnValues.concat(importForThemeSpy.returnValues))
+ .then(() => {
+ flush(done);
+ });
});
});
@@ -77,18 +86,13 @@
sandbox.restore();
});
- test('applies --primary-text-color', () => {
+ test('applies the right theme', () => {
assert.equal(
util.getComputedStyleValue('--primary-text-color', element),
'#F00BAA');
- });
-
- test('applies --header-background-color', () => {
assert.equal(
util.getComputedStyleValue('--header-background-color', element),
'#F01BAA');
- });
- test('applies --footer-background-color', () => {
assert.equal(
util.getComputedStyleValue('--footer-background-color', element),
'#F02BAA');
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-range-normalizer.js b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-range-normalizer.js
index eb4123d..556395c 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-range-normalizer.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-range-normalizer.js
@@ -54,7 +54,7 @@
if (element.nodeName === '#text') {
element = element.parentElement;
}
- while (!element.classList.contains('contentText')) {
+ while (element && !element.classList.contains('contentText')) {
if (element.parentElement === null) {
return target;
}
@@ -80,7 +80,7 @@
if (n === child) {
break;
}
- if (n.childNodes && n.childNodes.length !== 0) {
+ if (n && n.childNodes && n.childNodes.length !== 0) {
const arr = [];
for (const childNode of n.childNodes) {
arr.push(childNode);
@@ -101,7 +101,9 @@
* @return {number} The length of the text.
*/
_getLength(node) {
- return node.textContent.replace(REGEX_ASTRAL_SYMBOL, '_').length;
+ return node
+ ? node.textContent.replace(REGEX_ASTRAL_SYMBOL, '_').length
+ : 0;
},
};
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.js b/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.js
index 9a9a6d7..f6f48b3 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog.js
@@ -34,13 +34,13 @@
getFocusStops() {
return {
- start: this.$.contextSelect,
+ start: this.$.diffPreferences.$.contextSelect,
end: this.$.saveButton,
};
},
resetFocus() {
- this.$.contextSelect.focus();
+ this.$.diffPreferences.$.contextSelect.focus();
},
_computeHeaderClass(changed) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.html b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.html
index 83b6f55..6469382 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.html
@@ -20,6 +20,31 @@
<dom-module id="gr-diff-selection">
<template>
+ <style include="shared-styles">
+ /** Select and copy for Polymer 1*/
+ .contentWrapper ::content .content,
+ .contentWrapper ::content .contextControl,
+ .contentWrapper ::content .blame {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ }
+
+ :host-context(.selected-left:not(.selected-comment)) .contentWrapper ::content .side-by-side .left + .content .contentText,
+ :host-context(.selected-right:not(.selected-comment)) .contentWrapper ::content .side-by-side .right + .content .contentText,
+ :host-context(.selected-left:not(.selected-comment)) .contentWrapper ::content .unified .left.lineNum ~ .content:not(.both) .contentText,
+ :host-context(.selected-right:not(.selected-comment)) .contentWrapper ::content .unified .right.lineNum ~ .content .contentText,
+ :host-context(.selected-left.selected-comment) .contentWrapper ::content .side-by-side .left + .content .message,
+ :host-context(.selected-right.selected-comment) .contentWrapper ::content .side-by-side .right + .content .message :not(.collapsedContent),
+ :host-context(.selected-comment) .contentWrapper ::content .unified .message :not(.collapsedContent),
+ :host-context(.selected-blame) .contentWrapper ::content .blame {
+ -webkit-user-select: text;
+ -moz-user-select: text;
+ -ms-user-select: text;
+ user-select: text;
+ }
+ </style>
<div class="contentWrapper">
<slot></slot>
</div>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
index 072a0d7..ff7593b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
@@ -73,7 +73,28 @@
this._linesCache = getNewCache();
},
+ _handleDownOnRangeComment(node) {
+ // Keep the original behavior in polymer 1
+ if (!window.POLYMER2) return false;
+ if (node &&
+ node.nodeName &&
+ node.nodeName.toLowerCase() === 'gr-comment-thread') {
+ this._setClasses([
+ SelectionClass.COMMENT,
+ node.commentSide === 'left' ?
+ SelectionClass.LEFT :
+ SelectionClass.RIGHT,
+ ]);
+ return true;
+ }
+ return false;
+ },
+
_handleDown(e) {
+ // Handle the down event on comment thread in Polymer 2
+ const handled = this._handleDownOnRangeComment(e.target);
+ if (handled) return;
+
const lineEl = this.diffBuilder.getLineElByChild(e.target);
const blameSelected = this._elementDescendedFromClass(e.target, 'blame');
if (!lineEl && !blameSelected) { return; }
@@ -140,6 +161,10 @@
},
_handleCopy(e) {
+ // Let the browser handle the copy event for polymer 2
+ // as selection across shadow DOM will be hard to process
+ if (window.POLYMER2) return;
+
let commentSelected = false;
const target = this._getCopyEventTarget(e);
if (target.type === 'textarea') { return; }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
index cbb653b..bc8af9d 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
@@ -42,6 +42,7 @@
max-width: var(--content-width, 80ch);
white-space: normal;
}
+
.thread-group {
display: block;
max-width: var(--content-width, 80ch);
@@ -307,7 +308,8 @@
background: linear-gradient(to right bottom, #FFD1A4 0%, #FFD1A4 50%, #E0F2F1 50%, #E0F2F1 100%);
}
- /** Select to copy */
+ /** BEGIN: Select and copy for Polymer 2 */
+ /** Below was copied and modified from the original css in gr-diff-selection.html */
.content,
.contextControl,
.blame {
@@ -331,6 +333,16 @@
user-select: text;
}
+ /** Make comments selectable */
+ .selected-left ::slotted(gr-comment-thread[comment-side=left]),
+ .selected-right ::slotted(gr-comment-thread[comment-side=right]) {
+ -webkit-user-select: text;
+ -moz-user-select: text;
+ -ms-user-select: text;
+ user-select: text;
+ }
+ /** END: Select and copy for Polymer 2 */
+
.whitespace-change-only-message {
background-color: var(--diff-context-control-background-color);
border: 1px solid var(--diff-context-control-border-color);
diff --git a/polygerrit-ui/app/elements/gr-app-element.html b/polygerrit-ui/app/elements/gr-app-element.html
index 46c4502..a42622e 100644
--- a/polygerrit-ui/app/elements/gr-app-element.html
+++ b/polygerrit-ui/app/elements/gr-app-element.html
@@ -225,7 +225,8 @@
config="[[_serverConfig]]">
</gr-plugin-host>
<gr-lib-loader id="libLoader"></gr-lib-loader>
- <gr-external-style id="externalStyle" name="app-theme"></gr-external-style>
+ <gr-external-style id="externalStyleForAll" name="app-theme"></gr-external-style>
+ <gr-external-style id="externalStyleForTheme" name="[[getThemeEndpoint()]]"></gr-external-style>
</template>
<script src="gr-app-element.js" crossorigin="anonymous"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/gr-app-element.js b/polygerrit-ui/app/elements/gr-app-element.js
index 01da895..3c146f8 100644
--- a/polygerrit-ui/app/elements/gr-app-element.js
+++ b/polygerrit-ui/app/elements/gr-app-element.js
@@ -460,5 +460,12 @@
return false;
},
+
+ getThemeEndpoint() {
+ // For now, we only have dark mode and light mode
+ return window.localStorage.getItem('dark-theme') ?
+ 'app-theme-dark' :
+ 'app-theme-light';
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api.js b/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api.js
index 3959186..d1f8e56 100644
--- a/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api.js
+++ b/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api.js
@@ -30,8 +30,8 @@
* @param {string} text
* @param {string} url
*/
- GrAdminApi.prototype.addMenuLink = function(text, url) {
- this._menuLinks.push({text, url});
+ GrAdminApi.prototype.addMenuLink = function(text, url, opt_capability) {
+ this._menuLinks.push({text, url, capability: opt_capability || null});
};
GrAdminApi.prototype.getMenuLinks = function() {
diff --git a/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html b/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html
index 6883e7e..9cdcf76 100644
--- a/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html
@@ -56,7 +56,15 @@
adminApi.addMenuLink('text', 'url');
const links = adminApi.getMenuLinks();
assert.equal(links.length, 1);
- assert.deepEqual(links[0], {text: 'text', url: 'url'});
+ assert.deepEqual(links[0], {text: 'text', url: 'url', capability: null});
+ });
+
+ test('addMenuLinkWithCapability', () => {
+ adminApi.addMenuLink('text', 'url', 'capability');
+ const links = adminApi.getMenuLinks();
+ assert.equal(links.length, 1);
+ assert.deepEqual(links[0],
+ {text: 'text', url: 'url', capability: 'capability'});
});
});
</script>
diff --git a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.html b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.html
similarity index 79%
rename from polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.html
rename to polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.html
index 03fc606..ae656fd 100644
--- a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.html
@@ -15,12 +15,11 @@
limitations under the License.
-->
-<link rel="import" href="../../../behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html">
<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
-<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
-<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+<link rel="import" href="../gr-autocomplete/gr-autocomplete.html">
+<link rel="import" href="../gr-rest-api-interface/gr-rest-api-interface.html">
<dom-module id="gr-account-entry">
<template>
@@ -36,14 +35,13 @@
borderless="[[borderless]]"
placeholder="[[placeholder]]"
threshold="[[suggestFrom]]"
- query="[[query]]"
+ query="[[querySuggestions]]"
allow-non-suggested-values="[[allowAnyInput]]"
on-commit="_handleInputCommit"
clear-on-commit
warn-uncommitted
text="{{_inputText}}">
</gr-autocomplete>
- <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
<script src="gr-account-entry.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.js b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.js
new file mode 100644
index 0000000..63bd252
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.js
@@ -0,0 +1,102 @@
+/**
+ * @license
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function() {
+ 'use strict';
+
+ /**
+ * gr-account-entry is an element for entering account
+ * and/or group with autocomplete support.
+ */
+ Polymer({
+ is: 'gr-account-entry',
+ _legacyUndefinedCheck: true,
+
+ /**
+ * Fired when an account is entered.
+ *
+ * @event add
+ */
+
+ /**
+ * When allowAnyInput is true, account-text-changed is fired when input text
+ * changed. This is needed so that the reply dialog's save button can be
+ * enabled for arbitrary cc's, which don't need a 'commit'.
+ *
+ * @event account-text-changed
+ */
+ properties: {
+ allowAnyInput: Boolean,
+ borderless: Boolean,
+ placeholder: String,
+
+ // suggestFrom = 0 to enable default suggestions.
+ suggestFrom: {
+ type: Number,
+ value: 0,
+ },
+
+ /** @type {!function(string): !Promise<Array<{name, value}>>} */
+ querySuggestions: {
+ type: Function,
+ notify: true,
+ value() {
+ return input => Promise.resolve([]);
+ },
+ },
+
+ _config: Object,
+ /** The value of the autocomplete entry. */
+ _inputText: {
+ type: String,
+ observer: '_inputTextChanged',
+ },
+
+ },
+
+ get focusStart() {
+ return this.$.input.focusStart;
+ },
+
+ focus() {
+ this.$.input.focus();
+ },
+
+ clear() {
+ this.$.input.clear();
+ },
+
+ setText(text) {
+ this.$.input.setText(text);
+ },
+
+ getText() {
+ return this.$.input.text;
+ },
+
+ _handleInputCommit(e) {
+ this.fire('add', {value: e.detail.value});
+ this.$.input.focus();
+ },
+
+ _inputTextChanged(text) {
+ if (text.length && this.allowAnyInput) {
+ this.dispatchEvent(new CustomEvent(
+ 'account-text-changed', {bubbles: true, composed: true}));
+ }
+ },
+ });
+})();
diff --git a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html
new file mode 100644
index 0000000..59792a7
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2016 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-account-entry</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<script src="../../../scripts/util.js"></script>
+
+<link rel="import" href="gr-account-entry.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-account-entry></gr-account-entry>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-account-entry tests', () => {
+ let sandbox;
+
+ const suggestion1 = {
+ email: 'email1@example.com',
+ _account_id: 1,
+ some_property: 'value',
+ };
+ const suggestion2 = {
+ email: 'email2@example.com',
+ _account_id: 2,
+ };
+ const suggestion3 = {
+ email: 'email25@example.com',
+ _account_id: 25,
+ some_other_property: 'other value',
+ };
+
+ setup(done => {
+ element = fixture('basic');
+ sandbox = sinon.sandbox.create();
+ return flush(done);
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ });
+
+ suite('stubbed values for querySuggestions', () => {
+ setup(() => {
+ element.querySuggestions = input => {
+ return Promise.resolve([
+ suggestion1,
+ suggestion2,
+ suggestion3,
+ ]);
+ };
+ });
+ });
+
+ test('account-text-changed fired when input text changed and allowAnyInput',
+ () => {
+ // Spy on query, as that is called when _updateSuggestions proceeds.
+ const changeStub = sandbox.stub();
+ element.allowAnyInput = true;
+ element.querySuggestions = input => Promise.resolve([]);
+ element.addEventListener('account-text-changed', changeStub);
+ element.$.input.text = 'a';
+ assert.isTrue(changeStub.calledOnce);
+ element.$.input.text = 'ab';
+ assert.isTrue(changeStub.calledTwice);
+ });
+
+ test('account-text-changed not fired when input text changed without ' +
+ 'allowAnyInput', () => {
+ // Spy on query, as that is called when _updateSuggestions proceeds.
+ const changeStub = sandbox.stub();
+ element.querySuggestions = input => Promise.resolve([]);
+ element.addEventListener('account-text-changed', changeStub);
+ element.$.input.text = 'a';
+ assert.isFalse(changeStub.called);
+ });
+
+ test('setText', () => {
+ // Spy on query, as that is called when _updateSuggestions proceeds.
+ const suggestSpy = sandbox.spy(element.$.input, 'query');
+ element.setText('test text');
+ flushAsynchronousOperations();
+
+ assert.equal(element.$.input.$.input.value, 'test text');
+ assert.isFalse(suggestSpy.called);
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html
index 9d0782f..f807a74 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html
@@ -15,7 +15,7 @@
limitations under the License.
-->
-<link rel="import" href="../../../behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html">
+<link rel="import" href="../../../behaviors/gr-display-name-behavior/gr-display-name-behavior.html">
<link rel="import" href="../../../behaviors/gr-tooltip-behavior/gr-tooltip-behavior.html">
<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../styles/shared-styles.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js
index 88df33b..a778e8b 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js
@@ -52,7 +52,7 @@
},
behaviors: [
- Gerrit.AnonymousNameBehavior,
+ Gerrit.DisplayNameBehavior,
Gerrit.TooltipBehavior,
],
diff --git a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.html b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.html
similarity index 91%
rename from polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.html
rename to polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.html
index 31c1be5..c793c07 100644
--- a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.html
@@ -17,7 +17,7 @@
<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
-<link rel="import" href="../../shared/gr-account-chip/gr-account-chip.html">
+<link rel="import" href="../gr-account-chip/gr-account-chip.html">
<link rel="import" href="../gr-account-entry/gr-account-entry.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -56,7 +56,7 @@
account="[[account]]"
class$="[[_computeChipClass(account)]]"
data-account-id$="[[account._account_id]]"
- removable="[[_computeRemovable(account)]]"
+ removable="[[_computeRemovable(account, readonly)]]"
on-keydown="_handleChipKeydown"
tabindex="-1">
</gr-account-chip>
@@ -67,13 +67,13 @@
hidden$="[[_computeEntryHidden(maxCount, accounts.*, readonly)]]"
id="entry"
change="[[change]]"
- filter="[[filter]]"
placeholder="[[placeholder]]"
on-add="_handleAdd"
on-input-keydown="_handleInputKeydown"
allow-any-input="[[allowAnyInput]]"
- allow-any-user="[[allowAnyUser]]">
+ query-suggestions="[[_querySuggestions]]">
</gr-account-entry>
+ <slot></slot>
</template>
<script src="gr-account-list.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js
similarity index 79%
rename from polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js
rename to polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js
index 479fee2..9edc9c8 100644
--- a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js
@@ -19,6 +19,24 @@
const VALID_EMAIL_ALERT = 'Please input a valid email.';
+ const Defs = {};
+
+ /**
+ * @typedef {{
+ * name: string,
+ * value: Object,
+ * }}
+ */
+ Defs.GrSuggestionItem;
+
+ /**
+ * @typedef {{
+ * getSuggestions: function(string): Promise<Array<Object>>,
+ * makeSuggestionItem: function(Object): Defs.GrSuggestionItem,
+ * }}
+ */
+ Defs.GrSuggestionsProvider;
+
Polymer({
is: 'gr-account-list',
_legacyUndefinedCheck: true,
@@ -38,6 +56,19 @@
change: Object,
filter: Function,
placeholder: String,
+ disabled: {
+ type: Function,
+ value: false,
+ },
+
+ /**
+ * Returns suggestions and convert them to list item
+ * @type {Defs.GrSuggestionsProvider}
+ */
+ suggestionsProvider: {
+ type: Object,
+ },
+
/**
* Needed for template checking since value is initially set to null.
* @type {?Object} */
@@ -50,21 +81,6 @@
type: Boolean,
value: false,
},
-
- /**
- * When true, the account-entry autocomplete uses the account suggest API
- * endpoint, which suggests any account in that Gerrit instance (and does
- * not suggest groups).
- *
- * When false/undefined, account-entry uses the suggest_reviewers API
- * endpoint, which suggests any account or group in that Gerrit instance
- * that is not already a reviewer (or is not CCed) on that change.
- */
- allowAnyUser: {
- type: Boolean,
- value: false,
- },
-
/**
* When true, allows for non-suggested inputs to be added.
*/
@@ -82,6 +98,16 @@
type: Number,
value: 0,
},
+
+ /** Returns suggestion items
+ * @type {!function(string): Promise<Array<Defs.GrSuggestionItem>>}
+ */
+ _querySuggestions: {
+ type: Function,
+ value() {
+ return this._getSuggestions.bind(this);
+ },
+ },
},
behaviors: [
@@ -103,31 +129,46 @@
return this.$.entry.focusStart;
},
- _handleAdd(e) {
- this._addReviewer(e.detail.value);
+ _getSuggestions(input) {
+ const provider = this.suggestionsProvider;
+ if (!provider) {
+ return Promise.resolve([]);
+ }
+ return provider.getSuggestions(input).then(suggestions => {
+ if (!suggestions) { return []; }
+ if (this.filter) {
+ suggestions = suggestions.filter(this.filter);
+ }
+ return suggestions.map(suggestion =>
+ provider.makeSuggestionItem(suggestion));
+ });
},
- _addReviewer(reviewer) {
+ _handleAdd(e) {
+ this._addAccountItem(e.detail.value);
+ },
+
+ _addAccountItem(item) {
// Append new account or group to the accounts property. We add our own
// internal properties to the account/group here, so we clone the object
// to avoid cluttering up the shared change object.
- if (reviewer.account) {
+ if (item.account) {
const account =
- Object.assign({}, reviewer.account, {_pendingAdd: true});
+ Object.assign({}, item.account, {_pendingAdd: true});
this.push('accounts', account);
- } else if (reviewer.group) {
- if (reviewer.confirm) {
- this.pendingConfirmation = reviewer;
+ } else if (item.group) {
+ if (item.confirm) {
+ this.pendingConfirmation = item;
return;
}
- const group = Object.assign({}, reviewer.group,
+ const group = Object.assign({}, item.group,
{_pendingAdd: true, _group: true});
this.push('accounts', group);
} else if (this.allowAnyInput) {
- if (!reviewer.includes('@')) {
+ if (!item.includes('@')) {
// Repopulate the input with what the user tried to enter and have
// a toast tell them why they can't enter it.
- this.$.entry.setText(reviewer);
+ this.$.entry.setText(item);
this.dispatchEvent(new CustomEvent('show-alert', {
detail: {message: VALID_EMAIL_ALERT},
bubbles: true,
@@ -135,7 +176,7 @@
}));
return false;
} else {
- const account = {email: reviewer, _pendingAdd: true};
+ const account = {email: item, _pendingAdd: true};
this.push('accounts', account);
}
}
@@ -173,8 +214,8 @@
return a === b;
},
- _computeRemovable(account) {
- if (this.readonly) { return false; }
+ _computeRemovable(account, readonly) {
+ if (readonly) { return false; }
if (this.removableValues) {
for (let i = 0; i < this.removableValues.length; i++) {
if (this._accountMatches(this.removableValues[i], account)) {
@@ -193,7 +234,9 @@
},
_removeAccount(toRemove) {
- if (!toRemove || !this._computeRemovable(toRemove)) { return; }
+ if (!toRemove || !this._computeRemovable(toRemove, this.readonly)) {
+ return;
+ }
for (let i = 0; i < this.accounts.length; i++) {
let matches;
const account = this.accounts[i];
@@ -277,7 +320,7 @@
submitEntryText() {
const text = this.$.entry.getText();
if (!text.length) { return true; }
- const wasSubmitted = this._addReviewer(text);
+ const wasSubmitted = this._addAccountItem(text);
if (wasSubmitted) { this.$.entry.clear(); }
return wasSubmitted;
},
diff --git a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html
similarity index 77%
rename from polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html
rename to polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html
index d32c269..22e3a3d 100644
--- a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html
@@ -35,6 +35,15 @@
</test-fixture>
<script>
+ class MockSuggestionsProvider {
+ getSuggestions(input) {
+ return Promise.resolve([]);
+ }
+
+ makeSuggestionItem(item) {
+ return item;
+ }
+ }
suite('gr-account-list tests', () => {
let _nextAccountId = 0;
const makeAccount = function() {
@@ -51,10 +60,11 @@
};
};
- let existingReviewer1;
- let existingReviewer2;
+ let existingAccount1;
+ let existingAccount2;
let sandbox;
let element;
+ let suggestionsProvider;
function getChips() {
return Polymer.dom(element.root).querySelectorAll('gr-account-chip');
@@ -62,14 +72,16 @@
setup(() => {
sandbox = sinon.sandbox.create();
- existingReviewer1 = makeAccount();
- existingReviewer2 = makeAccount();
+ existingAccount1 = makeAccount();
+ existingAccount2 = makeAccount();
stub('gr-rest-api-interface', {
getConfig() { return Promise.resolve({}); },
});
element = fixture('basic');
- element.accounts = [existingReviewer1, existingReviewer2];
+ element.accounts = [existingAccount1, existingAccount2];
+ suggestionsProvider = new MockSuggestionsProvider();
+ element.suggestionsProvider = suggestionsProvider;
});
teardown(() => {
@@ -109,7 +121,7 @@
assert.isTrue(chips[2].classList.contains('pendingAdd'));
// Removed accounts are taken out of the list.
- element.fire('remove', {account: existingReviewer1});
+ element.fire('remove', {account: existingAccount1});
flushAsynchronousOperations();
chips = getChips();
assert.equal(chips.length, 2);
@@ -117,7 +129,7 @@
assert.isTrue(chips[1].classList.contains('pendingAdd'));
// Invalid remove is ignored.
- element.fire('remove', {account: existingReviewer1});
+ element.fire('remove', {account: existingAccount1});
element.fire('remove', {account: newAccount});
flushAsynchronousOperations();
chips = getChips();
@@ -147,6 +159,52 @@
assert.isFalse(chips[0].classList.contains('pendingAdd'));
});
+ test('_getSuggestions uses filter correctly', done => {
+ const originalSuggestions = [
+ {
+ email: 'abc@example.com',
+ text: 'abcd',
+ _account_id: 3,
+ },
+ {
+ email: 'qwe@example.com',
+ text: 'qwer',
+ _account_id: 1,
+ },
+ {
+ email: 'xyz@example.com',
+ text: 'aaaaa',
+ _account_id: 25,
+ },
+ ];
+ sandbox.stub(suggestionsProvider, 'getSuggestions')
+ .returns(Promise.resolve(originalSuggestions));
+ sandbox.stub(suggestionsProvider, 'makeSuggestionItem', suggestion => {
+ return {
+ name: suggestion.email,
+ value: suggestion._account_id,
+ };
+ });
+
+
+ element._getSuggestions().then(suggestions => {
+ // Default is no filtering.
+ assert.equal(suggestions.length, 3);
+
+ // Set up filter that only accepts suggestion1.
+ const accountId = originalSuggestions[0]._account_id;
+ element.filter = function(suggestion) {
+ return suggestion._account_id === accountId;
+ };
+
+ element._getSuggestions().then(suggestions => {
+ assert.deepEqual(suggestions,
+ [{name: originalSuggestions[0].email,
+ value: originalSuggestions[0]._account_id}]);
+ }).then(done);
+ });
+ });
+
test('_computeChipClass', () => {
const account = makeAccount();
assert.equal(element._computeChipClass(account), '');
@@ -163,18 +221,18 @@
newAccount._pendingAdd = true;
element.readonly = false;
element.removableValues = [];
- assert.isFalse(element._computeRemovable(existingReviewer1));
- assert.isTrue(element._computeRemovable(newAccount));
+ assert.isFalse(element._computeRemovable(existingAccount1, false));
+ assert.isTrue(element._computeRemovable(newAccount, false));
- element.removableValues = [existingReviewer1];
- assert.isTrue(element._computeRemovable(existingReviewer1));
- assert.isTrue(element._computeRemovable(newAccount));
- assert.isFalse(element._computeRemovable(existingReviewer2));
+ element.removableValues = [existingAccount1];
+ assert.isTrue(element._computeRemovable(existingAccount1, false));
+ assert.isTrue(element._computeRemovable(newAccount, false));
+ assert.isFalse(element._computeRemovable(existingAccount2, false));
element.readonly = true;
- assert.isFalse(element._computeRemovable(existingReviewer1));
- assert.isFalse(element._computeRemovable(newAccount));
+ assert.isFalse(element._computeRemovable(existingAccount1, true));
+ assert.isFalse(element._computeRemovable(newAccount, true));
});
test('submitEntryText', () => {
@@ -293,13 +351,40 @@
assert.isTrue(element.$.entry.hasAttribute('hidden'));
});
- suite('allowAnyInput', () => {
- let entry;
+ test('enter text calls suggestions provider', done => {
+ const suggestions = [
+ {
+ email: 'abc@example.com',
+ text: 'abcd',
+ },
+ {
+ email: 'qwe@example.com',
+ text: 'qwer',
+ },
+ ];
+ const getSuggestionsStub =
+ sandbox.stub(suggestionsProvider, 'getSuggestions')
+ .returns(Promise.resolve(suggestions));
+ const makeSuggestionItemStub =
+ sandbox.stub(suggestionsProvider, 'makeSuggestionItem', item => item);
+
+ const input = element.$.entry.$.input;
+
+ input.text = 'newTest';
+ MockInteractions.focus(input.$.input);
+ input.noDebounce = true;
+ flushAsynchronousOperations();
+ flush(() => {
+ assert.isTrue(getSuggestionsStub.calledOnce);
+ assert.equal(getSuggestionsStub.lastCall.args[0], 'newTest');
+ assert.equal(makeSuggestionItemStub.getCalls().length, 2);
+ done();
+ });
+ });
+
+ suite('allowAnyInput', () => {
setup(() => {
- entry = element.$.entry;
- sandbox.stub(entry, '_getReviewerSuggestions');
- sandbox.stub(entry.$.input, '_updateSuggestions');
element.allowAnyInput = true;
});
@@ -334,7 +419,6 @@
suite('keyboard interactions', () => {
test('backspace at text input start removes last account', () => {
const input = element.$.entry.$.input;
- sandbox.stub(element.$.entry, '_getReviewerSuggestions');
sandbox.stub(input, '_updateSuggestions');
sandbox.stub(element, '_computeRemovable').returns(true);
// Next line is a workaround for Firefix not moving cursor
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html
index f747719..498dfb8 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html
@@ -232,6 +232,16 @@
#deleteBtn.showDeleteButtons {
display: block;
}
+
+ /** Disable select for the caret and actions */
+ .actions,
+ .show-hide {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ }
+
</style>
<div id="container" class="container">
<div class="header" id="header" on-tap="_handleToggleCollapsed">
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.js
index e885bd4..f494325 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.js
@@ -51,7 +51,15 @@
};
GrPluginRestApi.prototype.fetchJSON = function(req) {
- return getRestApi()._fetchJSON(req);
+ // TODO(dhruvsri): find better implementation for fetchJSON
+ const api = getRestApi();
+ let fetchJSON;
+ if (api._fetchJSON) {
+ fetchJSON = api._fetchJSON.bind(api);
+ } else {
+ fetchJSON = api._restApiHelper.fetchJSON.bind(api._restApiHelper);
+ }
+ return fetchJSON(req);
};
GrPluginRestApi.prototype.getRepos =
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.html
index 87ea02b..7461ac4 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.html
@@ -29,6 +29,7 @@
<dom-module id="gr-rest-api-interface">
<!-- NB: Order is important, because of namespaced classes. -->
+ <script src="gr-rest-apis/gr-rest-api-helper.js"></script>
<script src="gr-auth.js"></script>
<script src="gr-reviewer-updates-parser.js"></script>
<script src="gr-rest-api-interface.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index 9b63f75..417fa23 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -29,34 +29,6 @@
/**
* @typedef {{
- * url: string,
- * fetchOptions: (Object|null|undefined),
- * anonymizedUrl: (string|undefined),
- * }}
- */
- Defs.FetchRequest;
-
- /**
- * Object to describe a request for passing into _fetchJSON or _fetchRawJSON.
- * - url is the URL for the request (excluding get params)
- * - errFn is a function to invoke when the request fails.
- * - cancelCondition is a function that, if provided and returns true, will
- * cancel the response after it resolves.
- * - params is a key-value hash to specify get params for the request URL.
- * @typedef {{
- * url: string,
- * errFn: (function(?Response, string=)|null|undefined),
- * cancelCondition: (function()|null|undefined),
- * params: (Object|null|undefined),
- * fetchOptions: (Object|null|undefined),
- * anonymizedUrl: (string|undefined),
- * reportUrlAsIs: (boolean|undefined),
- * }}
- */
- Defs.FetchJSONRequest;
-
- /**
- * @typedef {{
* changeNum: (string|number),
* endpoint: string,
* patchNum: (string|number|null|undefined),
@@ -121,7 +93,6 @@
const MAX_PROJECT_RESULTS = 25;
const MAX_UNIFIED_DEFAULT_WINDOW_WIDTH_PX = 900;
const PARENT_PATCH_NUM = 'PARENT';
- const FAILED_TO_FETCH_ERROR = 'Failed to fetch';
const Requests = {
SEND_DIFF_DRAFT: 'sendDiffDraft',
@@ -135,60 +106,6 @@
const ANONYMIZED_REVISION_BASE_URL = ANONYMIZED_CHANGE_BASE_URL +
'/revisions/*';
- /**
- * Wrapper around Map for caching server responses. Site-based so that
- * changes to CANONICAL_PATH will result in a different cache going into
- * effect.
- */
- class SiteBasedCache {
- constructor() {
- // Container of per-canonical-path caches.
- this._data = new Map();
- if (window.INITIAL_DATA != undefined) {
- // Put all data shipped with index.html into the cache. This makes it
- // so that we spare more round trips to the server when the app loads
- // initially.
- Object
- .entries(window.INITIAL_DATA)
- .forEach(e => this._cache().set(e[0], e[1]));
- }
- }
-
- // Returns the cache for the current canonical path.
- _cache() {
- if (!this._data.has(window.CANONICAL_PATH)) {
- this._data.set(window.CANONICAL_PATH, new Map());
- }
- return this._data.get(window.CANONICAL_PATH);
- }
-
- has(key) {
- return this._cache().has(key);
- }
-
- get(key) {
- return this._cache().get(key);
- }
-
- set(key, value) {
- this._cache().set(key, value);
- }
-
- delete(key) {
- this._cache().delete(key);
- }
-
- invalidatePrefix(prefix) {
- const newMap = new Map();
- for (const [key, value] of this._cache().entries()) {
- if (!key.startsWith(prefix)) {
- newMap.set(key, value);
- }
- }
- this._data.set(window.CANONICAL_PATH, newMap);
- }
- }
-
Polymer({
is: 'gr-rest-api-interface',
_legacyUndefinedCheck: true,
@@ -235,7 +152,7 @@
},
_sharedFetchPromises: {
type: Object,
- value: {}, // Intentional to share the object across instances.
+ value: new FetchPromisesCache(), // Shared across instances.
},
_pendingRequests: {
type: Object,
@@ -260,132 +177,48 @@
JSON_PREFIX,
- /**
- * Wraps calls to the underlying authenticated fetch function (_auth.fetch)
- * with timing and logging.
- * @param {Defs.FetchRequest} req
- */
- _fetch(req) {
- const start = Date.now();
- const xhr = this._auth.fetch(req.url, req.fetchOptions);
+ created() {
+ /* Polymer 1 and Polymer 2 have slightly different lifecycle.
+ * Differences are not very well documented (see
+ * https://github.com/Polymer/old-docs-site/issues/2322).
+ * In Polymer 1, created() is called when properties values is not set
+ * and ready() is always called later, even if element is not added
+ * to a DOM. I.e. in Polymer 1 _cache and other properties are undefined,
+ * while in Polymer 2 they are set to default values.
+ * In Polymer 2, created() is called after properties values set and
+ * ready() is called only after element is attached to a DOM.
+ * There are several places in the code, where element is created with
+ * document.createElement('gr-rest-api-interface') and is not added
+ * to a DOM.
+ * In such cases, Polymer 1 calls both created() and ready() methods,
+ * but Polymer 2 calls only created() method.
+ * To workaround these differences, we should try to create _restApiHelper
+ * in both methods.
+ */
+ //
- // Log the call after it completes.
- xhr.then(res => this._logCall(req, start, res.status));
-
- // Return the XHR directly (without the log).
- return xhr;
+ this._initRestApiHelper();
},
- /**
- * Log information about a REST call. Because the elapsed time is determined
- * by this method, it should be called immediately after the request
- * finishes.
- * @param {Defs.FetchRequest} req
- * @param {number} startTime the time that the request was started.
- * @param {number} status the HTTP status of the response. The status value
- * is used here rather than the response object so there is no way this
- * method can read the body stream.
- */
- _logCall(req, startTime, status) {
- const method = (req.fetchOptions && req.fetchOptions.method) ?
- req.fetchOptions.method : 'GET';
- const endTime = Date.now();
- const elapsed = (endTime - startTime);
- const startAt = new Date(startTime);
- const endAt = new Date(endTime);
- console.log([
- 'HTTP',
- status,
- method,
- elapsed + 'ms',
- req.anonymizedUrl || req.url,
- `(${startAt.toISOString()}, ${endAt.toISOString()})`,
- ].join(' '));
- if (req.anonymizedUrl) {
- this.fire('rpc-log',
- {status, method, elapsed, anonymizedUrl: req.anonymizedUrl});
+ ready() {
+ // See comments in created()
+ this._initRestApiHelper();
+ },
+
+ _initRestApiHelper() {
+ if (this._restApiHelper) {
+ return;
+ }
+ if (this._cache && this._auth && this._sharedFetchPromises
+ && this._credentialCheck) {
+ this._restApiHelper = new GrRestApiHelper(this._cache, this._auth,
+ this._sharedFetchPromises, this._credentialCheck, this);
}
},
- /**
- * Fetch JSON from url provided.
- * Returns a Promise that resolves to a native Response.
- * Doesn't do error checking. Supports cancel condition. Performs auth.
- * Validates auth expiry errors.
- * @param {Defs.FetchJSONRequest} req
- */
- _fetchRawJSON(req) {
- const urlWithParams = this._urlWithParams(req.url, req.params);
- const fetchReq = {
- url: urlWithParams,
- fetchOptions: req.fetchOptions,
- anonymizedUrl: req.reportUrlAsIs ? urlWithParams : req.anonymizedUrl,
- };
- return this._fetch(fetchReq).then(res => {
- if (req.cancelCondition && req.cancelCondition()) {
- res.body.cancel();
- return;
- }
- return res;
- }).catch(err => {
- const isLoggedIn = !!this._cache.get('/accounts/self/detail');
- if (isLoggedIn && err && err.message === FAILED_TO_FETCH_ERROR) {
- this.checkCredentials();
- } else {
- if (req.errFn) {
- req.errFn.call(undefined, null, err);
- } else {
- this.fire('network-error', {error: err});
- }
- }
- throw err;
- });
- },
-
- /**
- * Fetch JSON from url provided.
- * Returns a Promise that resolves to a parsed response.
- * Same as {@link _fetchRawJSON}, plus error handling.
- * @param {Defs.FetchJSONRequest} req
- */
- _fetchJSON(req) {
- req = this._addAcceptJsonHeader(req);
- return this._fetchRawJSON(req).then(response => {
- if (!response) {
- return;
- }
- if (!response.ok) {
- if (req.errFn) {
- req.errFn.call(null, response);
- return;
- }
- this.fire('server-error', {request: req, response});
- return;
- }
- return response && this.getResponseObject(response);
- });
- },
-
- /**
- * @param {string} url
- * @param {?Object|string=} opt_params URL params, key-value hash.
- * @return {string}
- */
- _urlWithParams(url, opt_params) {
- if (!opt_params) { return this.getBaseUrl() + url; }
-
- const params = [];
- for (const p in opt_params) {
- if (!opt_params.hasOwnProperty(p)) { continue; }
- if (opt_params[p] == null) {
- params.push(encodeURIComponent(p));
- continue;
- }
- for (const value of [].concat(opt_params[p])) {
- params.push(`${encodeURIComponent(p)}=${encodeURIComponent(value)}`);
- }
- }
- return this.getBaseUrl() + url + '?' + params.join('&');
+ _fetchSharedCacheURL(req) {
+ // Cache is shared across instances
+ return this._restApiHelper.fetchCacheURL(req);
},
/**
@@ -393,45 +226,7 @@
* @return {?}
*/
getResponseObject(response) {
- return this._readResponsePayload(response)
- .then(payload => payload.parsed);
- },
-
- /**
- * @param {!Object} response
- * @return {!Object}
- */
- _readResponsePayload(response) {
- return response.text().then(text => {
- let result;
- try {
- result = this._parsePrefixedJSON(text);
- } catch (_) {
- result = null;
- }
- return {parsed: result, raw: text};
- });
- },
-
- /**
- * @param {string} source
- * @return {?}
- */
- _parsePrefixedJSON(source) {
- return JSON.parse(source.substring(JSON_PREFIX.length));
- },
-
- /**
- * @param {Defs.FetchJSONRequest} req
- * @return {Defs.FetchJSONRequest}
- */
- _addAcceptJsonHeader(req) {
- if (!req.fetchOptions) req.fetchOptions = {};
- if (!req.fetchOptions.headers) req.fetchOptions.headers = new Headers();
- if (!req.fetchOptions.headers.has('Accept')) {
- req.fetchOptions.headers.append('Accept', 'application/json');
- }
- return req;
+ return this._restApiHelper.getResponseObject(response);
},
getConfig(noCache) {
@@ -442,7 +237,7 @@
});
}
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/config/server/info',
reportUrlAsIs: true,
});
@@ -492,7 +287,7 @@
// supports it.
const url = `/projects/${encodeURIComponent(repo)}/config`;
this._cache.delete(url);
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url,
body: config,
@@ -506,7 +301,7 @@
// TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
// supports it.
const encodeName = encodeURIComponent(repo);
- return this._send({
+ return this._restApiHelper.send({
method: 'POST',
url: `/projects/${encodeName}/gc`,
body: '',
@@ -524,7 +319,7 @@
// TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
// supports it.
const encodeName = encodeURIComponent(config.name);
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/projects/${encodeName}`,
body: config,
@@ -540,7 +335,7 @@
createGroup(config, opt_errFn) {
if (!config.name) { return ''; }
const encodeName = encodeURIComponent(config.name);
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/groups/${encodeName}`,
body: config,
@@ -550,7 +345,7 @@
},
getGroupConfig(group, opt_errFn) {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: `/groups/${encodeURIComponent(group)}/detail`,
errFn: opt_errFn,
anonymizedUrl: '/groups/*/detail',
@@ -568,7 +363,7 @@
// supports it.
const encodeName = encodeURIComponent(repo);
const encodeRef = encodeURIComponent(ref);
- return this._send({
+ return this._restApiHelper.send({
method: 'DELETE',
url: `/projects/${encodeName}/branches/${encodeRef}`,
body: '',
@@ -588,7 +383,7 @@
// supports it.
const encodeName = encodeURIComponent(repo);
const encodeRef = encodeURIComponent(ref);
- return this._send({
+ return this._restApiHelper.send({
method: 'DELETE',
url: `/projects/${encodeName}/tags/${encodeRef}`,
body: '',
@@ -609,7 +404,7 @@
// supports it.
const encodeName = encodeURIComponent(name);
const encodeBranch = encodeURIComponent(branch);
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/projects/${encodeName}/branches/${encodeBranch}`,
body: revision,
@@ -630,7 +425,7 @@
// supports it.
const encodeName = encodeURIComponent(name);
const encodeTag = encodeURIComponent(tag);
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/projects/${encodeName}/tags/${encodeTag}`,
body: revision,
@@ -655,7 +450,7 @@
getGroupMembers(groupName, opt_errFn) {
const encodeName = encodeURIComponent(groupName);
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: `/groups/${encodeName}/members/`,
errFn: opt_errFn,
anonymizedUrl: '/groups/*/members',
@@ -663,7 +458,7 @@
},
getIncludedGroup(groupName) {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: `/groups/${encodeURIComponent(groupName)}/groups/`,
anonymizedUrl: '/groups/*/groups',
});
@@ -671,7 +466,7 @@
saveGroupName(groupId, name) {
const encodeId = encodeURIComponent(groupId);
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/groups/${encodeId}/name`,
body: {name},
@@ -681,7 +476,7 @@
saveGroupOwner(groupId, ownerId) {
const encodeId = encodeURIComponent(groupId);
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/groups/${encodeId}/owner`,
body: {owner: ownerId},
@@ -691,7 +486,7 @@
saveGroupDescription(groupId, description) {
const encodeId = encodeURIComponent(groupId);
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/groups/${encodeId}/description`,
body: {description},
@@ -701,7 +496,7 @@
saveGroupOptions(groupId, options) {
const encodeId = encodeURIComponent(groupId);
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/groups/${encodeId}/options`,
body: options,
@@ -720,7 +515,7 @@
saveGroupMembers(groupName, groupMembers) {
const encodeName = encodeURIComponent(groupName);
const encodeMember = encodeURIComponent(groupMembers);
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/groups/${encodeName}/members/${encodeMember}`,
parseResponse: true,
@@ -737,7 +532,7 @@
errFn: opt_errFn,
anonymizedUrl: '/groups/*/groups/*',
};
- return this._send(req).then(response => {
+ return this._restApiHelper.send(req).then(response => {
if (response.ok) {
return this.getResponseObject(response);
}
@@ -747,7 +542,7 @@
deleteGroupMembers(groupName, groupMembers) {
const encodeName = encodeURIComponent(groupName);
const encodeMember = encodeURIComponent(groupMembers);
- return this._send({
+ return this._restApiHelper.send({
method: 'DELETE',
url: `/groups/${encodeName}/members/${encodeMember}`,
anonymizedUrl: '/groups/*/members/*',
@@ -757,7 +552,7 @@
deleteIncludedGroup(groupName, includedGroup) {
const encodeName = encodeURIComponent(groupName);
const encodeIncludedGroup = encodeURIComponent(includedGroup);
- return this._send({
+ return this._restApiHelper.send({
method: 'DELETE',
url: `/groups/${encodeName}/groups/${encodeIncludedGroup}`,
anonymizedUrl: '/groups/*/groups/*',
@@ -844,7 +639,7 @@
prefs.download_scheme = prefs.download_scheme.toLowerCase();
}
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: '/accounts/self/preferences',
body: prefs,
@@ -860,7 +655,7 @@
saveDiffPreferences(prefs, opt_errFn) {
// Invalidate the cache.
this._cache.delete('/accounts/self/preferences.diff');
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: '/accounts/self/preferences.diff',
body: prefs,
@@ -876,7 +671,7 @@
saveEditPreferences(prefs, opt_errFn) {
// Invalidate the cache.
this._cache.delete('/accounts/self/preferences.edit');
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: '/accounts/self/preferences.edit',
body: prefs,
@@ -910,14 +705,14 @@
},
getExternalIds() {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/accounts/self/external.ids',
reportUrlAsIs: true,
});
},
deleteAccountIdentity(id) {
- return this._send({
+ return this._restApiHelper.send({
method: 'POST',
url: '/accounts/self/external.ids:delete',
body: id,
@@ -931,7 +726,7 @@
* @return {!Promise<!Object>}
*/
getAccountDetails(userId) {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: `/accounts/${encodeURIComponent(userId)}/detail`,
anonymizedUrl: '/accounts/*/detail',
});
@@ -949,7 +744,7 @@
* @param {function(?Response, string=)=} opt_errFn
*/
addAccountEmail(email, opt_errFn) {
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: '/accounts/self/emails/' + encodeURIComponent(email),
errFn: opt_errFn,
@@ -962,7 +757,7 @@
* @param {function(?Response, string=)=} opt_errFn
*/
deleteAccountEmail(email, opt_errFn) {
- return this._send({
+ return this._restApiHelper.send({
method: 'DELETE',
url: '/accounts/self/emails/' + encodeURIComponent(email),
errFn: opt_errFn,
@@ -982,7 +777,7 @@
errFn: opt_errFn,
anonymizedUrl: '/accounts/self/emails/*/preferred',
};
- return this._send(req).then(() => {
+ return this._restApiHelper.send(req).then(() => {
// If result of getAccountEmails is in cache, update it in the cache
// so we don't have to invalidate it.
const cachedEmails = this._cache.get('/accounts/self/emails');
@@ -1026,7 +821,7 @@
parseResponse: true,
reportUrlAsIs: true,
};
- return this._send(req)
+ return this._restApiHelper.send(req)
.then(newName => this._updateCachedAccount({name: newName}));
},
@@ -1043,7 +838,7 @@
parseResponse: true,
reportUrlAsIs: true,
};
- return this._send(req)
+ return this._restApiHelper.send(req)
.then(newName => this._updateCachedAccount({username: newName}));
},
@@ -1060,33 +855,33 @@
parseResponse: true,
reportUrlAsIs: true,
};
- return this._send(req)
+ return this._restApiHelper.send(req)
.then(newStatus => this._updateCachedAccount({status: newStatus}));
},
getAccountStatus(userId) {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: `/accounts/${encodeURIComponent(userId)}/status`,
anonymizedUrl: '/accounts/*/status',
});
},
getAccountGroups() {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/accounts/self/groups',
reportUrlAsIs: true,
});
},
getAccountAgreements() {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/accounts/self/agreements',
reportUrlAsIs: true,
});
},
saveAccountAgreement(name) {
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: '/accounts/self/agreements',
body: name,
@@ -1131,34 +926,7 @@
},
checkCredentials() {
- if (this._credentialCheck.checking) {
- return;
- }
- this._credentialCheck.checking = true;
- let req = {url: '/accounts/self/detail', reportUrlAsIs: true};
- req = this._addAcceptJsonHeader(req);
- // Skip the REST response cache.
- return this._fetchRawJSON(req).then(res => {
- if (!res) { return; }
- if (res.status === 403) {
- this.fire('auth-error');
- this._cache.delete('/accounts/self/detail');
- } else if (res.ok) {
- return this.getResponseObject(res);
- }
- }).then(res => {
- this._credentialCheck.checking = false;
- if (res) {
- this._cache.set('/accounts/self/detail', res);
- }
- return res;
- }).catch(err => {
- this._credentialCheck.checking = false;
- if (err && err.message === FAILED_TO_FETCH_ERROR) {
- this.fire('auth-error');
- this._cache.delete('/accounts/self/detail');
- }
- });
+ return this._restApiHelper.checkCredentials();
},
getDefaultPreferences() {
@@ -1204,7 +972,7 @@
* @param {function(?Response, string=)=} opt_errFn
*/
saveWatchedProjects(projects, opt_errFn) {
- return this._send({
+ return this._restApiHelper.send({
method: 'POST',
url: '/accounts/self/watched.projects',
body: projects,
@@ -1219,7 +987,7 @@
* @param {function(?Response, string=)=} opt_errFn
*/
deleteWatchedProjects(projects, opt_errFn) {
- return this._send({
+ return this._restApiHelper.send({
method: 'POST',
url: '/accounts/self/watched.projects:delete',
body: projects,
@@ -1228,45 +996,6 @@
});
},
- /**
- * @param {Defs.FetchJSONRequest} req
- */
- _fetchSharedCacheURL(req) {
- if (this._sharedFetchPromises[req.url]) {
- return this._sharedFetchPromises[req.url];
- }
- // TODO(andybons): Periodic cache invalidation.
- if (this._cache.has(req.url)) {
- return Promise.resolve(this._cache.get(req.url));
- }
- this._sharedFetchPromises[req.url] = this._fetchJSON(req)
- .then(response => {
- if (response !== undefined) {
- this._cache.set(req.url, response);
- }
- this._sharedFetchPromises[req.url] = undefined;
- return response;
- }).catch(err => {
- this._sharedFetchPromises[req.url] = undefined;
- throw err;
- });
- return this._sharedFetchPromises[req.url];
- },
-
- /**
- * @param {string} prefix
- */
- _invalidateSharedFetchPromisesPrefix(prefix) {
- const newObject = {};
- Object.entries(this._sharedFetchPromises).forEach(([key, value]) => {
- if (!key.startsWith(prefix)) {
- newObject[key] = value;
- }
- });
- this._sharedFetchPromises = newObject;
- this._cache.invalidatePrefix(prefix);
- },
-
_isNarrowScreen() {
return window.innerWidth < MAX_UNIFIED_DEFAULT_WINDOW_WIDTH_PX;
},
@@ -1308,7 +1037,7 @@
params,
reportUrlAsIs: true,
};
- return this._fetchJSON(req).then(response => {
+ return this._restApiHelper.fetchJSON(req).then(response => {
// Response may be an array of changes OR an array of arrays of
// changes.
if (opt_query instanceof Array) {
@@ -1407,7 +1136,8 @@
*/
_getChangeDetail(changeNum, optionsHex, opt_errFn, opt_cancelCondition) {
return this.getChangeActionURL(changeNum, null, '/detail').then(url => {
- const urlWithParams = this._urlWithParams(url, optionsHex);
+ const urlWithParams = this._restApiHelper
+ .urlWithParams(url, optionsHex);
const params = {O: optionsHex};
let req = {
url,
@@ -1417,10 +1147,10 @@
fetchOptions: this._etags.getOptions(urlWithParams),
anonymizedUrl: '/changes/*~*/detail?O=' + optionsHex,
};
- req = this._addAcceptJsonHeader(req);
- return this._fetchRawJSON(req).then(response => {
+ req = this._restApiHelper.addAcceptJsonHeader(req);
+ return this._restApiHelper.fetchRawJSON(req).then(response => {
if (response && response.status === 304) {
- return Promise.resolve(this._parsePrefixedJSON(
+ return Promise.resolve(this._restApiHelper.parsePrefixedJSON(
this._etags.getCachedPayload(urlWithParams)));
}
@@ -1434,7 +1164,7 @@
}
const payloadPromise = response ?
- this._readResponsePayload(response) :
+ this._restApiHelper.readResponsePayload(response) :
Promise.resolve(null);
return payloadPromise.then(payload => {
@@ -1647,11 +1377,11 @@
},
invalidateGroupsCache() {
- this._invalidateSharedFetchPromisesPrefix('/groups/?');
+ this._restApiHelper.invalidateFetchPromisesPrefix('/groups/?');
},
invalidateReposCache() {
- this._invalidateSharedFetchPromisesPrefix('/projects/?');
+ this._restApiHelper.invalidateFetchPromisesPrefix('/projects/?');
},
/**
@@ -1689,7 +1419,7 @@
setRepoHead(repo, ref) {
// TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
// supports it.
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/projects/${encodeURIComponent(repo)}/HEAD`,
body: {ref},
@@ -1713,7 +1443,7 @@
const url = `/projects/${repo}/branches?n=${count}&S=${offset}${filter}`;
// TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
// supports it.
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url,
errFn: opt_errFn,
anonymizedUrl: '/projects/*/branches?*',
@@ -1737,7 +1467,7 @@
encodedFilter;
// TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
// supports it.
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url,
errFn: opt_errFn,
anonymizedUrl: '/projects/*/tags',
@@ -1756,7 +1486,7 @@
const encodedFilter = this._computeFilter(filter);
const n = pluginsPerPage + 1;
const url = `/plugins/?all&n=${n}&S=${offset}${encodedFilter}`;
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url,
errFn: opt_errFn,
anonymizedUrl: '/plugins/?all',
@@ -1766,7 +1496,7 @@
getRepoAccessRights(repoName, opt_errFn) {
// TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
// supports it.
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: `/projects/${encodeURIComponent(repoName)}/access`,
errFn: opt_errFn,
anonymizedUrl: '/projects/*/access',
@@ -1776,7 +1506,7 @@
setRepoAccessRights(repoName, repoInfo) {
// TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
// supports it.
- return this._send({
+ return this._restApiHelper.send({
method: 'POST',
url: `/projects/${encodeURIComponent(repoName)}/access`,
body: repoInfo,
@@ -1785,7 +1515,7 @@
},
setRepoAccessRightsForReview(projectName, projectInfo) {
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: `/projects/${encodeURIComponent(projectName)}/access:review`,
body: projectInfo,
@@ -1802,7 +1532,7 @@
getSuggestedGroups(inputVal, opt_n, opt_errFn) {
const params = {s: inputVal};
if (opt_n) { params.n = opt_n; }
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/groups/',
errFn: opt_errFn,
params,
@@ -1822,7 +1552,7 @@
type: 'ALL',
};
if (opt_n) { params.n = opt_n; }
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/projects/',
errFn: opt_errFn,
params,
@@ -1841,7 +1571,7 @@
}
const params = {suggest: null, q: inputVal};
if (opt_n) { params.n = opt_n; }
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/accounts/',
errFn: opt_errFn,
params,
@@ -1872,7 +1602,7 @@
throw Error('Unsupported HTTP method: ' + method);
}
- return this._send({method, url, body});
+ return this._restApiHelper.send({method, url, body});
});
},
@@ -1902,7 +1632,7 @@
O: options,
q: 'status:open is:mergeable conflicts:' + changeNum,
};
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/changes/',
params,
anonymizedUrl: '/changes/conflicts:*',
@@ -1924,7 +1654,7 @@
O: options,
q: query,
};
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/changes/',
params,
anonymizedUrl: '/changes/change:*',
@@ -1947,7 +1677,7 @@
O: options,
q: query,
};
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/changes/',
params,
anonymizedUrl: '/changes/topic:*',
@@ -1993,7 +1723,7 @@
this.getChangeActionURL(changeNum, patchNum, '/review'),
];
return Promise.all(promises).then(([, url]) => {
- return this._send({
+ return this._restApiHelper.send({
method: 'POST',
url,
body: review,
@@ -2027,7 +1757,7 @@
*/
createChange(project, branch, subject, opt_topic, opt_isPrivate,
opt_workInProgress, opt_baseChange, opt_baseCommit) {
- return this._send({
+ return this._restApiHelper.send({
method: 'POST',
url: '/changes/',
body: {
@@ -2202,7 +1932,7 @@
return this.getFromProjectLookup(changeNum).then(project => {
const url = '/accounts/self/starred.changes/' +
(project ? encodeURIComponent(project) + '~' : '') + changeNum;
- return this._send({
+ return this._restApiHelper.send({
method: starred ? 'PUT' : 'DELETE',
url,
anonymizedUrl: '/accounts/self/starred.changes/*',
@@ -2219,59 +1949,7 @@
},
/**
- * Send an XHR.
- * @param {Defs.SendRequest} req
- * @return {Promise}
- */
- _send(req) {
- const options = {method: req.method};
- if (req.body) {
- options.headers = new Headers();
- options.headers.set(
- 'Content-Type', req.contentType || 'application/json');
- options.body = typeof req.body === 'string' ?
- req.body : JSON.stringify(req.body);
- }
- if (req.headers) {
- if (!options.headers) { options.headers = new Headers(); }
- for (const header in req.headers) {
- if (!req.headers.hasOwnProperty(header)) { continue; }
- options.headers.set(header, req.headers[header]);
- }
- }
- const url = req.url.startsWith('http') ?
- req.url : this.getBaseUrl() + req.url;
- const fetchReq = {
- url,
- fetchOptions: options,
- anonymizedUrl: req.reportUrlAsIs ? url : req.anonymizedUrl,
- };
- const xhr = this._fetch(fetchReq).then(response => {
- if (!response.ok) {
- if (req.errFn) {
- return req.errFn.call(undefined, response);
- }
- this.fire('server-error', {request: fetchReq, response});
- }
- return response;
- }).catch(err => {
- this.fire('network-error', {error: err});
- if (req.errFn) {
- return req.errFn.call(undefined, null, err);
- } else {
- throw err;
- }
- });
-
- if (req.parseResponse) {
- return xhr.then(res => this.getResponseObject(res));
- }
-
- return xhr;
- },
-
- /**
- * Public version of the _send method preserved for plugins.
+ * Public version of the _restApiHelper.send method preserved for plugins.
* @param {string} method
* @param {string} url
* @param {?string|number|Object=} opt_body passed as null sometimes
@@ -2284,7 +1962,7 @@
*/
send(method, url, opt_body, opt_errFn, opt_contentType,
opt_headers) {
- return this._send({
+ return this._restApiHelper.send({
method,
url,
body: opt_body,
@@ -2541,7 +2219,7 @@
},
getCommitInfo(project, commit) {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/projects/' + encodeURIComponent(project) +
'/commits/' + encodeURIComponent(commit),
anonymizedUrl: '/projects/*/comments/*',
@@ -2549,7 +2227,7 @@
},
_fetchB64File(url) {
- return this._fetch({url: this.getBaseUrl() + url})
+ return this._restApiHelper.fetch({url: this.getBaseUrl() + url})
.then(response => {
if (!response.ok) {
return Promise.reject(new Error(response.statusText));
@@ -2673,7 +2351,7 @@
},
deleteAccountHttpPassword() {
- return this._send({
+ return this._restApiHelper.send({
method: 'DELETE',
url: '/accounts/self/password.http',
reportUrlAsIs: true,
@@ -2686,7 +2364,7 @@
* parameter.
*/
generateAccountHttpPassword() {
- return this._send({
+ return this._restApiHelper.send({
method: 'PUT',
url: '/accounts/self/password.http',
body: {generate: true},
@@ -2710,7 +2388,7 @@
contentType: 'plain/text',
reportUrlAsIs: true,
};
- return this._send(req)
+ return this._restApiHelper.send(req)
.then(response => {
if (response.status < 200 && response.status >= 300) {
return Promise.reject(new Error('error'));
@@ -2724,7 +2402,7 @@
},
deleteAccountSSHKey(id) {
- return this._send({
+ return this._restApiHelper.send({
method: 'DELETE',
url: '/accounts/self/sshkeys/' + id,
anonymizedUrl: '/accounts/self/sshkeys/*',
@@ -2732,7 +2410,7 @@
},
getAccountGPGKeys() {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/accounts/self/gpgkeys',
reportUrlAsIs: true,
});
@@ -2745,7 +2423,7 @@
body: key,
reportUrlAsIs: true,
};
- return this._send(req)
+ return this._restApiHelper.send(req)
.then(response => {
if (response.status < 200 && response.status >= 300) {
return Promise.reject(new Error('error'));
@@ -2759,7 +2437,7 @@
},
deleteAccountGPGKey(id) {
- return this._send({
+ return this._restApiHelper.send({
method: 'DELETE',
url: '/accounts/self/gpgkeys/' + id,
anonymizedUrl: '/accounts/self/gpgkeys/*',
@@ -2792,7 +2470,7 @@
body: {token},
reportUrlAsIs: true,
};
- return this._send(req).then(response => {
+ return this._restApiHelper.send(req).then(response => {
if (response.status === 204) {
return 'Email confirmed successfully.';
}
@@ -2801,7 +2479,7 @@
},
getCapabilities(opt_errFn) {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: '/config/server/capabilities',
errFn: opt_errFn,
reportUrlAsIs: true,
@@ -2907,7 +2585,7 @@
*/
getChange(changeNum, opt_errFn) {
// Cannot use _changeBaseURL, as this function is used by _projectLookup.
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: `/changes/?q=change:${changeNum}`,
errFn: opt_errFn,
anonymizedUrl: '/changes/?q=change:*',
@@ -2967,7 +2645,7 @@
req.endpoint : req.anonymizedEndpoint;
return this._changeBaseURL(req.changeNum, req.patchNum).then(url => {
- return this._send({
+ return this._restApiHelper.send({
method: req.method,
url: url + req.endpoint,
body: req.body,
@@ -2992,7 +2670,7 @@
const anonymizedBaseUrl = req.patchNum ?
ANONYMIZED_REVISION_BASE_URL : ANONYMIZED_CHANGE_BASE_URL;
return this._changeBaseURL(req.changeNum, req.patchNum).then(url => {
- return this._fetchJSON({
+ return this._restApiHelper.fetchJSON({
url: url + req.endpoint,
errFn: req.errFn,
params: req.params,
@@ -3119,7 +2797,7 @@
},
deleteDraftComments(query) {
- return this._send({
+ return this._restApiHelper.send({
method: 'POST',
url: '/accounts/self/drafts:delete',
body: {query},
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index 9d0d83a..ea71522 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -63,116 +63,8 @@
sandbox.restore();
});
- suite('fetchJSON()', () => {
- test('Sets header to accept application/json', () => {
- const authFetchStub = sandbox.stub(element._auth, 'fetch')
- .returns(Promise.resolve());
- element._fetchJSON({url: '/dummy/url'});
- assert.isTrue(authFetchStub.called);
- assert.equal(authFetchStub.lastCall.args[1].headers.get('Accept'),
- 'application/json');
- });
-
- test('Use header option accept when provided', () => {
- const authFetchStub = sandbox.stub(element._auth, 'fetch')
- .returns(Promise.resolve());
- const headers = new Headers();
- headers.append('Accept', '*/*');
- const fetchOptions = {headers};
- element._fetchJSON({url: '/dummy/url', fetchOptions});
- assert.isTrue(authFetchStub.called);
- assert.equal(authFetchStub.lastCall.args[1].headers.get('Accept'),
- '*/*');
- });
- });
-
- test('JSON prefix is properly removed', done => {
- element._fetchJSON({url: '/dummy/url'}).then(obj => {
- assert.deepEqual(obj, {hello: 'bonjour'});
- done();
- });
- });
-
- test('cached results', done => {
- let n = 0;
- sandbox.stub(element, '_fetchJSON', () => {
- return Promise.resolve(++n);
- });
- const promises = [];
- promises.push(element._fetchSharedCacheURL('/foo'));
- promises.push(element._fetchSharedCacheURL('/foo'));
- promises.push(element._fetchSharedCacheURL('/foo'));
-
- Promise.all(promises).then(results => {
- assert.deepEqual(results, [1, 1, 1]);
- element._fetchSharedCacheURL('/foo').then(foo => {
- assert.equal(foo, 1);
- done();
- });
- });
- });
-
- test('cached promise', done => {
- const promise = Promise.reject(new Error('foo'));
- element._cache.set('/foo', promise);
- element._fetchSharedCacheURL({url: '/foo'}).catch(p => {
- assert.equal(p.message, 'foo');
- done();
- });
- });
-
- test('cache invalidation', () => {
- element._cache.set('/foo/bar', 1);
- element._cache.set('/bar', 2);
- element._sharedFetchPromises['/foo/bar'] = 3;
- element._sharedFetchPromises['/bar'] = 4;
- element._invalidateSharedFetchPromisesPrefix('/foo/');
- assert.isFalse(element._cache.has('/foo/bar'));
- assert.isTrue(element._cache.has('/bar'));
- assert.isUndefined(element._sharedFetchPromises['/foo/bar']);
- assert.strictEqual(4, element._sharedFetchPromises['/bar']);
- });
-
- test('params are properly encoded', () => {
- let url = element._urlWithParams('/path/', {
- sp: 'hola',
- gr: 'guten tag',
- noval: null,
- });
- assert.equal(url,
- window.CANONICAL_PATH + '/path/?sp=hola&gr=guten%20tag&noval');
-
- url = element._urlWithParams('/path/', {
- sp: 'hola',
- en: ['hey', 'hi'],
- });
- assert.equal(url, window.CANONICAL_PATH + '/path/?sp=hola&en=hey&en=hi');
-
- // Order must be maintained with array params.
- url = element._urlWithParams('/path/', {
- l: ['c', 'b', 'a'],
- });
- assert.equal(url, window.CANONICAL_PATH + '/path/?l=c&l=b&l=a');
- });
-
- test('request callbacks can be canceled', done => {
- let cancelCalled = false;
- window.fetch.returns(Promise.resolve({
- body: {
- cancel() { cancelCalled = true; },
- },
- }));
- const cancelCondition = () => { return true; };
- element._fetchJSON({url: '/dummy/url', cancelCondition}).then(
- obj => {
- assert.isUndefined(obj);
- assert.isTrue(cancelCalled);
- done();
- });
- });
-
test('parent diff comments are properly grouped', done => {
- sandbox.stub(element, '_fetchJSON', () => {
+ sandbox.stub(element._restApiHelper, 'fetchJSON', () => {
return Promise.resolve({
'/COMMIT_MSG': [],
'sieve.go': [
@@ -315,7 +207,7 @@
test('differing patch diff comments are properly grouped', done => {
sandbox.stub(element, 'getFromProjectLookup')
.returns(Promise.resolve('test'));
- sandbox.stub(element, '_fetchJSON', request => {
+ sandbox.stub(element._restApiHelper, 'fetchJSON', request => {
const url = request.url;
if (url === '/changes/test~42/revisions/1') {
return Promise.resolve({
@@ -432,7 +324,7 @@
suite('rebase action', () => {
let resolve_fetchJSON;
setup(() => {
- sandbox.stub(element, '_fetchJSON').returns(
+ sandbox.stub(element._restApiHelper, 'fetchJSON').returns(
new Promise(resolve => {
resolve_fetchJSON = resolve;
}));
@@ -467,7 +359,7 @@
element.addEventListener('server-error', resolve);
});
- element._fetchJSON({}).then(response => {
+ element._restApiHelper.fetchJSON({}).then(response => {
assert.isUndefined(response);
assert.isTrue(getResponseObjectStub.notCalled);
serverErrorEventPromise.then(() => done());
@@ -483,12 +375,12 @@
Promise.reject(new Error('Failed to fetch')));
window.fetch.onSecondCall().returns(Promise.resolve(fakeAuthResponse));
// Emulate logged in.
- element._cache.set('/accounts/self/detail', {});
+ element._restApiHelper._cache.set('/accounts/self/detail', {});
const serverErrorStub = sandbox.stub();
element.addEventListener('server-error', serverErrorStub);
const authErrorStub = sandbox.stub();
element.addEventListener('auth-error', authErrorStub);
- element._fetchJSON({url: '/bar'}).finally(r => {
+ element._restApiHelper.fetchJSON({url: '/bar'}).finally(r => {
flush(() => {
assert.isTrue(authErrorStub.called);
assert.isFalse(serverErrorStub.called);
@@ -507,7 +399,7 @@
element.addEventListener('server-error', serverErrorStub);
const authErrorStub = sandbox.stub();
element.addEventListener('auth-error', authErrorStub);
- element._fetchJSON({url: '/bar'}).finally(r => {
+ element._restApiHelper.fetchJSON({url: '/bar'}).finally(r => {
flush(() => {
assert.isTrue(authErrorStub.called);
assert.isFalse(serverErrorStub.called);
@@ -558,7 +450,8 @@
test('checkCredentials promise rejection', () => {
window.fetch.restore();
element._cache.set('/accounts/self/detail', true);
- sandbox.spy(element, 'checkCredentials');
+ const checkCredentialsSpy =
+ sandbox.spy(element._restApiHelper, 'checkCredentials');
sandbox.stub(window, 'fetch', url => {
return Promise.reject(new Error('Failed to fetch'));
});
@@ -570,7 +463,7 @@
// The second fetch call also fails, which leads to a second
// invocation of checkCredentials, which should immediately
// return instead of making further fetch calls.
- assert.isTrue(element.checkCredentials.calledTwice);
+ assert.isTrue(checkCredentialsSpy .calledTwice);
assert.isTrue(window.fetch.calledTwice);
});
});
@@ -585,7 +478,7 @@
});
test('legacy n,z key in change url is replaced', () => {
- const stub = sandbox.stub(element, '_fetchJSON')
+ const stub = sandbox.stub(element._restApiHelper, 'fetchJSON')
.returns(Promise.resolve([]));
element.getChanges(1, null, 'n,z');
assert.equal(stub.lastCall.args[0].params.S, 0);
@@ -593,38 +486,38 @@
test('saveDiffPreferences invalidates cache line', () => {
const cacheKey = '/accounts/self/preferences.diff';
- sandbox.stub(element, '_send');
+ const sendStub = sandbox.stub(element._restApiHelper, 'send');
element._cache.set(cacheKey, {tab_size: 4});
element.saveDiffPreferences({tab_size: 8});
- assert.isTrue(element._send.called);
- assert.isFalse(element._cache.has(cacheKey));
+ assert.isTrue(sendStub.called);
+ assert.isFalse(element._restApiHelper._cache.has(cacheKey));
});
test('getAccount when resp is null does not add anything to the cache',
done => {
const cacheKey = '/accounts/self/detail';
- const stub = sandbox.stub(element, '_fetchSharedCacheURL',
+ const stub = sandbox.stub(element._restApiHelper, 'fetchCacheURL',
() => Promise.resolve());
element.getAccount().then(() => {
- assert.isTrue(element._fetchSharedCacheURL.called);
- assert.isFalse(element._cache.has(cacheKey));
+ assert.isTrue(stub.called);
+ assert.isFalse(element._restApiHelper._cache.has(cacheKey));
done();
});
- element._cache.set(cacheKey, 'fake cache');
+ element._restApiHelper._cache.set(cacheKey, 'fake cache');
stub.lastCall.args[0].errFn();
});
test('getAccount does not add to the cache when resp.status is 403',
done => {
const cacheKey = '/accounts/self/detail';
- const stub = sandbox.stub(element, '_fetchSharedCacheURL',
+ const stub = sandbox.stub(element._restApiHelper, 'fetchCacheURL',
() => Promise.resolve());
element.getAccount().then(() => {
- assert.isTrue(element._fetchSharedCacheURL.called);
- assert.isFalse(element._cache.has(cacheKey));
+ assert.isTrue(stub.called);
+ assert.isFalse(element._restApiHelper._cache.has(cacheKey));
done();
});
element._cache.set(cacheKey, 'fake cache');
@@ -633,15 +526,15 @@
test('getAccount when resp is successful', done => {
const cacheKey = '/accounts/self/detail';
- const stub = sandbox.stub(element, '_fetchSharedCacheURL',
+ const stub = sandbox.stub(element._restApiHelper, 'fetchCacheURL',
() => Promise.resolve());
element.getAccount().then(response => {
- assert.isTrue(element._fetchSharedCacheURL.called);
- assert.equal(element._cache.get(cacheKey), 'fake cache');
+ assert.isTrue(stub.called);
+ assert.equal(element._restApiHelper._cache.get(cacheKey), 'fake cache');
done();
});
- element._cache.set(cacheKey, 'fake cache');
+ element._restApiHelper._cache.set(cacheKey, 'fake cache');
stub.lastCall.args[0].errFn({});
});
@@ -653,7 +546,7 @@
sandbox.stub(element, '_isNarrowScreen', () => {
return smallScreen;
});
- sandbox.stub(element, '_fetchSharedCacheURL', () => {
+ sandbox.stub(element._restApiHelper, 'fetchCacheURL', () => {
return Promise.resolve(testJSON);
});
};
@@ -718,10 +611,10 @@
});
test('savPreferences normalizes download scheme', () => {
- sandbox.stub(element, '_send');
+ const sendStub = sandbox.stub(element._restApiHelper, 'send');
element.savePreferences({download_scheme: 'HTTP'});
- assert.isTrue(element._send.called);
- assert.equal(element._send.lastCall.args[0].body.download_scheme, 'http');
+ assert.isTrue(sendStub.called);
+ assert.equal(sendStub.lastCall.args[0].body.download_scheme, 'http');
});
test('getDiffPreferences returns correct defaults', done => {
@@ -747,10 +640,10 @@
});
test('saveDiffPreferences set show_tabs to false', () => {
- sandbox.stub(element, '_send');
+ const sendStub = sandbox.stub(element._restApiHelper, 'send');
element.saveDiffPreferences({show_tabs: false});
- assert.isTrue(element._send.called);
- assert.equal(element._send.lastCall.args[0].body.show_tabs, false);
+ assert.isTrue(sendStub.called);
+ assert.equal(sendStub.lastCall.args[0].body.show_tabs, false);
});
test('getEditPreferences returns correct defaults', done => {
@@ -780,34 +673,36 @@
});
test('saveEditPreferences set show_tabs to false', () => {
- sandbox.stub(element, '_send');
+ const sendStub = sandbox.stub(element._restApiHelper, 'send');
element.saveEditPreferences({show_tabs: false});
- assert.isTrue(element._send.called);
- assert.equal(element._send.lastCall.args[0].body.show_tabs, false);
+ assert.isTrue(sendStub.called);
+ assert.equal(sendStub.lastCall.args[0].body.show_tabs, false);
});
test('confirmEmail', () => {
- sandbox.spy(element, '_send');
+ const sendStub = sandbox.spy(element._restApiHelper, 'send');
element.confirmEmail('foo');
- assert.isTrue(element._send.calledOnce);
- assert.equal(element._send.lastCall.args[0].method, 'PUT');
- assert.equal(element._send.lastCall.args[0].url,
+ assert.isTrue(sendStub.calledOnce);
+ assert.equal(sendStub.lastCall.args[0].method, 'PUT');
+ assert.equal(sendStub.lastCall.args[0].url,
'/config/server/email.confirm');
- assert.deepEqual(element._send.lastCall.args[0].body, {token: 'foo'});
+ assert.deepEqual(sendStub.lastCall.args[0].body, {token: 'foo'});
});
test('setAccountStatus', () => {
- sandbox.stub(element, '_send').returns(Promise.resolve('OOO'));
+ const sendStub = sandbox.stub(element._restApiHelper, 'send')
+ .returns(Promise.resolve('OOO'));
element._cache.set('/accounts/self/detail', {});
return element.setAccountStatus('OOO').then(() => {
- assert.isTrue(element._send.calledOnce);
- assert.equal(element._send.lastCall.args[0].method, 'PUT');
- assert.equal(element._send.lastCall.args[0].url,
+ assert.isTrue(sendStub.calledOnce);
+ assert.equal(sendStub.lastCall.args[0].method, 'PUT');
+ assert.equal(sendStub.lastCall.args[0].url,
'/accounts/self/status');
- assert.deepEqual(element._send.lastCall.args[0].body,
+ assert.deepEqual(sendStub.lastCall.args[0].body,
{status: 'OOO'});
- assert.deepEqual(element._cache.get('/accounts/self/detail'),
- {status: 'OOO'});
+ assert.deepEqual(element._restApiHelper
+ ._cache.get('/accounts/self/detail'),
+ {status: 'OOO'});
});
});
@@ -896,18 +791,20 @@
const change_num = '1';
const file_name = 'index.php';
const file_contents = '<?php';
- sandbox.stub(element, '_send').returns(
+ sandbox.stub(element._restApiHelper, 'send').returns(
Promise.resolve([change_num, file_name, file_contents]));
sandbox.stub(element, 'getResponseObject')
.returns(Promise.resolve([change_num, file_name, file_contents]));
element._cache.set('/changes/' + change_num + '/edit/' + file_name, {});
return element.saveChangeEdit(change_num, file_name, file_contents)
.then(() => {
- assert.isTrue(element._send.calledOnce);
- assert.equal(element._send.lastCall.args[0].method, 'PUT');
- assert.equal(element._send.lastCall.args[0].url,
+ assert.isTrue(element._restApiHelper.send.calledOnce);
+ assert.equal(element._restApiHelper.send.lastCall.args[0].method,
+ 'PUT');
+ assert.equal(element._restApiHelper.send.lastCall.args[0].url,
'/changes/test~1/edit/' + file_name);
- assert.equal(element._send.lastCall.args[0].body, file_contents);
+ assert.equal(element._restApiHelper.send.lastCall.args[0].body,
+ file_contents);
});
});
@@ -915,17 +812,18 @@
element._projectLookup = {1: 'test'};
const change_num = '1';
const message = 'this is a commit message';
- sandbox.stub(element, '_send').returns(
+ sandbox.stub(element._restApiHelper, 'send').returns(
Promise.resolve([change_num, message]));
sandbox.stub(element, 'getResponseObject')
.returns(Promise.resolve([change_num, message]));
element._cache.set('/changes/' + change_num + '/message', {});
return element.putChangeCommitMessage(change_num, message).then(() => {
- assert.isTrue(element._send.calledOnce);
- assert.equal(element._send.lastCall.args[0].method, 'PUT');
- assert.equal(element._send.lastCall.args[0].url,
+ assert.isTrue(element._restApiHelper.send.calledOnce);
+ assert.equal(element._restApiHelper.send.lastCall.args[0].method, 'PUT');
+ assert.equal(element._restApiHelper.send.lastCall.args[0].url,
'/changes/test~1/message');
- assert.deepEqual(element._send.lastCall.args[0].body, {message});
+ assert.deepEqual(element._restApiHelper.send.lastCall.args[0].body,
+ {message});
});
});
@@ -981,7 +879,7 @@
});
test('createRepo encodes name', () => {
- const sendStub = sandbox.stub(element, '_send')
+ const sendStub = sandbox.stub(element._restApiHelper, 'send')
.returns(Promise.resolve());
return element.createRepo({name: 'x/y'}).then(() => {
assert.isTrue(sendStub.calledOnce);
@@ -1027,64 +925,65 @@
suite('getRepos', () => {
const defaultQuery = 'state%3Aactive%20OR%20state%3Aread-only';
-
+ let fetchCacheURLStub;
setup(() => {
- sandbox.stub(element, '_fetchSharedCacheURL');
+ fetchCacheURLStub =
+ sandbox.stub(element._restApiHelper, 'fetchCacheURL');
});
test('normal use', () => {
element.getRepos('test', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&query=test');
element.getRepos(null, 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
`/projects/?n=26&S=0&query=${defaultQuery}`);
element.getRepos('test', 25, 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=25&query=test');
});
test('with blank', () => {
element.getRepos('test/test', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&query=inname%3Atest%20AND%20inname%3Atest');
});
test('with hyphen', () => {
element.getRepos('foo-bar', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
});
test('with leading hyphen', () => {
element.getRepos('-bar', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&query=inname%3Abar');
});
test('with trailing hyphen', () => {
element.getRepos('foo-bar-', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
});
test('with underscore', () => {
element.getRepos('foo_bar', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
});
test('with underscore', () => {
element.getRepos('foo_bar', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
});
test('hyphen only', () => {
element.getRepos('-', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
`/projects/?n=26&S=0&query=${defaultQuery}`);
});
});
@@ -1113,43 +1012,45 @@
});
suite('getGroups', () => {
+ let fetchCacheURLStub;
setup(() => {
- sandbox.stub(element, '_fetchSharedCacheURL');
+ fetchCacheURLStub =
+ sandbox.stub(element._restApiHelper, 'fetchCacheURL');
});
test('normal use', () => {
element.getGroups('test', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/groups/?n=26&S=0&m=test');
element.getGroups(null, 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/groups/?n=26&S=0');
element.getGroups('test', 25, 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/groups/?n=26&S=25&m=test');
});
test('regex', () => {
element.getGroups('^test.*', 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/groups/?n=26&S=0&r=%5Etest.*');
element.getGroups('^test.*', 25, 25);
- assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
'/groups/?n=26&S=25&r=%5Etest.*');
});
});
test('gerrit auth is used', () => {
sandbox.stub(Gerrit.Auth, 'fetch').returns(Promise.resolve());
- element._fetchJSON({url: 'foo'});
+ element._restApiHelper.fetchJSON({url: 'foo'});
assert(Gerrit.Auth.fetch.called);
});
test('getSuggestedAccounts does not return _fetchJSON', () => {
- const _fetchJSONSpy = sandbox.spy(element, '_fetchJSON');
+ const _fetchJSONSpy = sandbox.spy(element._restApiHelper, 'fetchJSON');
return element.getSuggestedAccounts().then(accts => {
assert.isFalse(_fetchJSONSpy.called);
assert.equal(accts.length, 0);
@@ -1157,7 +1058,7 @@
});
test('_fetchJSON gets called by getSuggestedAccounts', () => {
- const _fetchJSONStub = sandbox.stub(element, '_fetchJSON',
+ const _fetchJSONStub = sandbox.stub(element._restApiHelper, 'fetchJSON',
() => Promise.resolve());
return element.getSuggestedAccounts('own').then(() => {
assert.deepEqual(_fetchJSONStub.lastCall.args[0].params, {
@@ -1229,7 +1130,7 @@
const errFn = sinon.stub();
sandbox.stub(element, 'getChangeActionURL')
.returns(Promise.resolve(''));
- sandbox.stub(element, '_fetchRawJSON')
+ sandbox.stub(element._restApiHelper, 'fetchRawJSON')
.returns(Promise.resolve({ok: false, status: 500}));
return element._getChangeDetail(123, '516714', errFn).then(() => {
assert.isTrue(errFn.called);
@@ -1249,14 +1150,15 @@
test('_getChangeDetail populates _projectLookup', () => {
sandbox.stub(element, 'getChangeActionURL')
.returns(Promise.resolve(''));
- sandbox.stub(element, '_fetchRawJSON')
+ sandbox.stub(element._restApiHelper, 'fetchRawJSON')
.returns(Promise.resolve({ok: true}));
const mockResponse = {_number: 1, project: 'test'};
- sandbox.stub(element, '_readResponsePayload').returns(Promise.resolve({
- parsed: mockResponse,
- raw: JSON.stringify(mockResponse),
- }));
+ sandbox.stub(element._restApiHelper, 'readResponsePayload')
+ .returns(Promise.resolve({
+ parsed: mockResponse,
+ raw: JSON.stringify(mockResponse),
+ }));
return element._getChangeDetail(1, '516714').then(() => {
assert.equal(Object.keys(element._projectLookup).length, 1);
assert.equal(element._projectLookup[1], 'test');
@@ -1274,7 +1176,8 @@
const mockResponse = {foo: 'bar', baz: 42};
mockResponseSerial = element.JSON_PREFIX +
JSON.stringify(mockResponse);
- sandbox.stub(element, '_urlWithParams').returns(requestUrl);
+ sandbox.stub(element._restApiHelper, 'urlWithParams')
+ .returns(requestUrl);
sandbox.stub(element, 'getChangeActionURL')
.returns(Promise.resolve(requestUrl));
collectSpy = sandbox.spy(element._etags, 'collect');
@@ -1282,11 +1185,12 @@
});
test('contributes to cache', () => {
- sandbox.stub(element, '_fetchRawJSON').returns(Promise.resolve({
- text: () => Promise.resolve(mockResponseSerial),
- status: 200,
- ok: true,
- }));
+ sandbox.stub(element._restApiHelper, 'fetchRawJSON')
+ .returns(Promise.resolve({
+ text: () => Promise.resolve(mockResponseSerial),
+ status: 200,
+ ok: true,
+ }));
return element._getChangeDetail(123, '516714').then(detail => {
assert.isFalse(getPayloadSpy.called);
@@ -1297,11 +1201,12 @@
});
test('uses cache on HTTP 304', () => {
- sandbox.stub(element, '_fetchRawJSON').returns(Promise.resolve({
- text: () => Promise.resolve(mockResponseSerial),
- status: 304,
- ok: true,
- }));
+ sandbox.stub(element._restApiHelper, 'fetchRawJSON')
+ .returns(Promise.resolve({
+ text: () => Promise.resolve(mockResponseSerial),
+ status: 304,
+ ok: true,
+ }));
return element._getChangeDetail(123, {}).then(detail => {
assert.isFalse(collectSpy.called);
@@ -1346,7 +1251,7 @@
suite('getChanges populates _projectLookup', () => {
test('multiple queries', () => {
- sandbox.stub(element, '_fetchJSON')
+ sandbox.stub(element._restApiHelper, 'fetchJSON')
.returns(Promise.resolve([
[
{_number: 1, project: 'test'},
@@ -1366,7 +1271,7 @@
});
test('no query', () => {
- sandbox.stub(element, '_fetchJSON')
+ sandbox.stub(element._restApiHelper, 'fetchJSON')
.returns(Promise.resolve([
{_number: 1, project: 'test'},
{_number: 2, project: 'test'},
@@ -1386,7 +1291,7 @@
test('_getChangeURLAndFetch', () => {
element._projectLookup = {1: 'test'};
- const fetchStub = sandbox.stub(element, '_fetchJSON')
+ const fetchStub = sandbox.stub(element._restApiHelper, 'fetchJSON')
.returns(Promise.resolve());
const req = {changeNum: 1, endpoint: '/test', patchNum: 1};
return element._getChangeURLAndFetch(req).then(() => {
@@ -1397,7 +1302,7 @@
test('_getChangeURLAndSend', () => {
element._projectLookup = {1: 'test'};
- const sendStub = sandbox.stub(element, '_send')
+ const sendStub = sandbox.stub(element._restApiHelper, 'send')
.returns(Promise.resolve());
const req = {
@@ -1419,16 +1324,17 @@
const mockObject = {foo: 'bar', baz: 'foo'};
const serial = element.JSON_PREFIX + JSON.stringify(mockObject);
const mockResponse = {text: () => Promise.resolve(serial)};
- return element._readResponsePayload(mockResponse).then(payload => {
- assert.deepEqual(payload.parsed, mockObject);
- assert.equal(payload.raw, serial);
- });
+ return element._restApiHelper.readResponsePayload(mockResponse)
+ .then(payload => {
+ assert.deepEqual(payload.parsed, mockObject);
+ assert.equal(payload.raw, serial);
+ });
});
test('_parsePrefixedJSON', () => {
const obj = {x: 3, y: {z: 4}, w: 23};
const serial = element.JSON_PREFIX + JSON.stringify(obj);
- const result = element._parsePrefixedJSON(serial);
+ const result = element._restApiHelper.parsePrefixedJSON(serial);
assert.deepEqual(result, obj);
});
});
@@ -1450,7 +1356,7 @@
});
test('generateAccountHttpPassword', () => {
- const sendSpy = sandbox.spy(element, '_send');
+ const sendSpy = sandbox.spy(element._restApiHelper, 'send');
return element.generateAccountHttpPassword().then(() => {
assert.isTrue(sendSpy.calledOnce);
assert.deepEqual(sendSpy.lastCall.args[0].body, {generate: true});
@@ -1535,11 +1441,12 @@
});
test('getDashboard', () => {
- const fetchStub = sandbox.stub(element, '_fetchSharedCacheURL');
+ const fetchCacheURLStub = sandbox.stub(element._restApiHelper,
+ 'fetchCacheURL');
element.getDashboard('gerrit/project', 'default:main');
- assert.isTrue(fetchStub.calledOnce);
+ assert.isTrue(fetchCacheURLStub.calledOnce);
assert.equal(
- fetchStub.lastCall.args[0].url,
+ fetchCacheURLStub.lastCall.args[0].url,
'/projects/gerrit%2Fproject/dashboards/default%3Amain');
});
@@ -1607,7 +1514,7 @@
});
test('_fetch forwards request and logs', () => {
- const logStub = sandbox.stub(element, '_logCall');
+ const logStub = sandbox.stub(element._restApiHelper, '_logCall');
const response = {status: 404, text: sinon.stub()};
const url = 'my url';
const fetchOptions = {method: 'DELETE'};
@@ -1615,7 +1522,7 @@
const startTime = 123;
sandbox.stub(Date, 'now').returns(startTime);
const req = {url, fetchOptions};
- return element._fetch(req).then(() => {
+ return element._restApiHelper.fetch(req).then(() => {
assert.isTrue(logStub.calledOnce);
assert.isTrue(logStub.calledWith(req, startTime, response.status));
assert.isFalse(response.text.called);
@@ -1627,10 +1534,11 @@
const handler = sinon.stub();
element.addEventListener('rpc-log', handler);
- element._logCall({url: 'url'}, 100, 200);
+ element._restApiHelper._logCall({url: 'url'}, 100, 200);
assert.isFalse(handler.called);
- element._logCall({url: 'url', anonymizedUrl: 'not url'}, 100, 200);
+ element._restApiHelper
+ ._logCall({url: 'url', anonymizedUrl: 'not url'}, 100, 200);
flushAsynchronousOperations();
assert.isTrue(handler.calledOnce);
});
@@ -1639,7 +1547,7 @@
sandbox.stub(element, 'getFromProjectLookup')
.returns(Promise.resolve('test'));
const sendStub =
- sandbox.stub(element, '_send').returns(Promise.resolve());
+ sandbox.stub(element._restApiHelper, 'send').returns(Promise.resolve());
await element.saveChangeStarred(123, true);
assert.isTrue(sendStub.calledOnce);
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.js
new file mode 100644
index 0000000..d42abc3
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.js
@@ -0,0 +1,456 @@
+/**
+ * @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.
+ */
+(function(window) {
+ 'use strict';
+
+ const Defs = {};
+
+ /**
+ * @typedef {{
+ * url: string,
+ * fetchOptions: (Object|null|undefined),
+ * anonymizedUrl: (string|undefined),
+ * }}
+ */
+ Defs.FetchRequest;
+
+ /**
+ * Object to describe a request for passing into fetchJSON or fetchRawJSON.
+ * - url is the URL for the request (excluding get params)
+ * - errFn is a function to invoke when the request fails.
+ * - cancelCondition is a function that, if provided and returns true, will
+ * cancel the response after it resolves.
+ * - params is a key-value hash to specify get params for the request URL.
+ * @typedef {{
+ * url: string,
+ * errFn: (function(?Response, string=)|null|undefined),
+ * cancelCondition: (function()|null|undefined),
+ * params: (Object|null|undefined),
+ * fetchOptions: (Object|null|undefined),
+ * anonymizedUrl: (string|undefined),
+ * reportUrlAsIs: (boolean|undefined),
+ * }}
+ */
+ Defs.FetchJSONRequest;
+
+ const JSON_PREFIX = ')]}\'';
+ const FAILED_TO_FETCH_ERROR = 'Failed to fetch';
+
+ /**
+ * Wrapper around Map for caching server responses. Site-based so that
+ * changes to CANONICAL_PATH will result in a different cache going into
+ * effect.
+ */
+ class SiteBasedCache {
+ constructor() {
+ // Container of per-canonical-path caches.
+ this._data = new Map();
+ if (window.INITIAL_DATA != undefined) {
+ // Put all data shipped with index.html into the cache. This makes it
+ // so that we spare more round trips to the server when the app loads
+ // initially.
+ Object
+ .entries(window.INITIAL_DATA)
+ .forEach(e => this._cache().set(e[0], e[1]));
+ }
+ }
+
+ // Returns the cache for the current canonical path.
+ _cache() {
+ if (!this._data.has(window.CANONICAL_PATH)) {
+ this._data.set(window.CANONICAL_PATH, new Map());
+ }
+ return this._data.get(window.CANONICAL_PATH);
+ }
+
+ has(key) {
+ return this._cache().has(key);
+ }
+
+ get(key) {
+ return this._cache().get(key);
+ }
+
+ set(key, value) {
+ this._cache().set(key, value);
+ }
+
+ delete(key) {
+ this._cache().delete(key);
+ }
+
+ invalidatePrefix(prefix) {
+ const newMap = new Map();
+ for (const [key, value] of this._cache().entries()) {
+ if (!key.startsWith(prefix)) {
+ newMap.set(key, value);
+ }
+ }
+ this._data.set(window.CANONICAL_PATH, newMap);
+ }
+ }
+
+ class FetchPromisesCache {
+ constructor() {
+ this._data = {};
+ }
+
+ has(key) {
+ return !!this._data[key];
+ }
+
+ get(key) {
+ return this._data[key];
+ }
+
+ set(key, value) {
+ this._data[key] = value;
+ }
+
+ invalidatePrefix(prefix) {
+ const newData = {};
+ Object.entries(this._data).forEach(([key, value]) => {
+ if (!key.startsWith(prefix)) {
+ newData[key] = value;
+ }
+ });
+ this._data = newData;
+ }
+ }
+
+ class GrRestApiHelper {
+ /**
+ * @param {SiteBasedCache} cache
+ * @param {object} auth
+ * @param {FetchPromisesCache} fetchPromisesCache
+ * @param {object} credentialCheck
+ * @param {object} restApiInterface
+ */
+ constructor(cache, auth, fetchPromisesCache, credentialCheck,
+ restApiInterface) {
+ this._cache = cache;// TODO: make it public
+ this._auth = auth;
+ this._fetchPromisesCache = fetchPromisesCache;
+ this._credentialCheck = credentialCheck;
+ this._restApiInterface = restApiInterface;
+ }
+
+ /**
+ * Wraps calls to the underlying authenticated fetch function (_auth.fetch)
+ * with timing and logging.
+ * @param {Defs.FetchRequest} req
+ */
+ fetch(req) {
+ const start = Date.now();
+ const xhr = this._auth.fetch(req.url, req.fetchOptions);
+
+ // Log the call after it completes.
+ xhr.then(res => this._logCall(req, start, res ? res.status : null));
+
+ // Return the XHR directly (without the log).
+ return xhr;
+ }
+
+ /**
+ * Log information about a REST call. Because the elapsed time is determined
+ * by this method, it should be called immediately after the request
+ * finishes.
+ * @param {Defs.FetchRequest} req
+ * @param {number} startTime the time that the request was started.
+ * @param {number} status the HTTP status of the response. The status value
+ * is used here rather than the response object so there is no way this
+ * method can read the body stream.
+ */
+ _logCall(req, startTime, status) {
+ const method = (req.fetchOptions && req.fetchOptions.method) ?
+ req.fetchOptions.method : 'GET';
+ const endTime = Date.now();
+ const elapsed = (endTime - startTime);
+ const startAt = new Date(startTime);
+ const endAt = new Date(endTime);
+ console.log([
+ 'HTTP',
+ status,
+ method,
+ elapsed + 'ms',
+ req.anonymizedUrl || req.url,
+ `(${startAt.toISOString()}, ${endAt.toISOString()})`,
+ ].join(' '));
+ if (req.anonymizedUrl) {
+ this.fire('rpc-log',
+ {status, method, elapsed, anonymizedUrl: req.anonymizedUrl});
+ }
+ }
+
+ /**
+ * Fetch JSON from url provided.
+ * Returns a Promise that resolves to a native Response.
+ * Doesn't do error checking. Supports cancel condition. Performs auth.
+ * Validates auth expiry errors.
+ * @param {Defs.FetchJSONRequest} req
+ */
+ fetchRawJSON(req) {
+ const urlWithParams = this.urlWithParams(req.url, req.params);
+ const fetchReq = {
+ url: urlWithParams,
+ fetchOptions: req.fetchOptions,
+ anonymizedUrl: req.reportUrlAsIs ? urlWithParams : req.anonymizedUrl,
+ };
+ return this.fetch(fetchReq).then(res => {
+ if (req.cancelCondition && req.cancelCondition()) {
+ res.body.cancel();
+ return;
+ }
+ return res;
+ }).catch(err => {
+ const isLoggedIn = !!this._cache.get('/accounts/self/detail');
+ if (isLoggedIn && err && err.message === FAILED_TO_FETCH_ERROR) {
+ this.checkCredentials();
+ } else {
+ if (req.errFn) {
+ req.errFn.call(undefined, null, err);
+ } else {
+ this.fire('network-error', {error: err});
+ }
+ }
+ throw err;
+ });
+ }
+
+ /**
+ * Fetch JSON from url provided.
+ * Returns a Promise that resolves to a parsed response.
+ * Same as {@link fetchRawJSON}, plus error handling.
+ * @param {Defs.FetchJSONRequest} req
+ */
+ fetchJSON(req) {
+ req = this.addAcceptJsonHeader(req);
+ return this.fetchRawJSON(req).then(response => {
+ if (!response) {
+ return;
+ }
+ if (!response.ok) {
+ if (req.errFn) {
+ req.errFn.call(null, response);
+ return;
+ }
+ this.fire('server-error', {request: req, response});
+ return;
+ }
+ return response && this.getResponseObject(response);
+ });
+ }
+
+ /**
+ * @param {string} url
+ * @param {?Object|string=} opt_params URL params, key-value hash.
+ * @return {string}
+ */
+ urlWithParams(url, opt_params) {
+ if (!opt_params) { return this.getBaseUrl() + url; }
+
+ const params = [];
+ for (const p in opt_params) {
+ if (!opt_params.hasOwnProperty(p)) { continue; }
+ if (opt_params[p] == null) {
+ params.push(encodeURIComponent(p));
+ continue;
+ }
+ for (const value of [].concat(opt_params[p])) {
+ params.push(`${encodeURIComponent(p)}=${encodeURIComponent(value)}`);
+ }
+ }
+ return this.getBaseUrl() + url + '?' + params.join('&');
+ }
+
+ /**
+ * @param {!Object} response
+ * @return {?}
+ */
+ getResponseObject(response) {
+ return this.readResponsePayload(response)
+ .then(payload => payload.parsed);
+ }
+
+ /**
+ * @param {!Object} response
+ * @return {!Object}
+ */
+ readResponsePayload(response) {
+ return response.text().then(text => {
+ let result;
+ try {
+ result = this.parsePrefixedJSON(text);
+ } catch (_) {
+ result = null;
+ }
+ return {parsed: result, raw: text};
+ });
+ }
+
+ /**
+ * @param {string} source
+ * @return {?}
+ */
+ parsePrefixedJSON(source) {
+ return JSON.parse(source.substring(JSON_PREFIX.length));
+ }
+
+ /**
+ * @param {Defs.FetchJSONRequest} req
+ * @return {Defs.FetchJSONRequest}
+ */
+ addAcceptJsonHeader(req) {
+ if (!req.fetchOptions) req.fetchOptions = {};
+ if (!req.fetchOptions.headers) req.fetchOptions.headers = new Headers();
+ if (!req.fetchOptions.headers.has('Accept')) {
+ req.fetchOptions.headers.append('Accept', 'application/json');
+ }
+ return req;
+ }
+
+ getBaseUrl() {
+ return this._restApiInterface.getBaseUrl();
+ }
+
+ fire(type, detail, options) {
+ return this._restApiInterface.fire(type, detail, options);
+ }
+
+ /**
+ * @param {Defs.FetchJSONRequest} req
+ */
+ fetchCacheURL(req) {
+ if (this._fetchPromisesCache.has(req.url)) {
+ return this._fetchPromisesCache.get(req.url);
+ }
+ // TODO(andybons): Periodic cache invalidation.
+ if (this._cache.has(req.url)) {
+ return Promise.resolve(this._cache.get(req.url));
+ }
+ this._fetchPromisesCache.set(req.url,
+ this.fetchJSON(req).then(response => {
+ if (response !== undefined) {
+ this._cache.set(req.url, response);
+ }
+ this._fetchPromisesCache.set(req.url, undefined);
+ return response;
+ }).catch(err => {
+ this._fetchPromisesCache.set(req.url, undefined);
+ throw err;
+ })
+ );
+ return this._fetchPromisesCache.get(req.url);
+ }
+
+ /**
+ * Send an XHR.
+ * @param {Defs.SendRequest} req
+ * @return {Promise}
+ */
+ send(req) {
+ const options = {method: req.method};
+ if (req.body) {
+ options.headers = new Headers();
+ options.headers.set(
+ 'Content-Type', req.contentType || 'application/json');
+ options.body = typeof req.body === 'string' ?
+ req.body : JSON.stringify(req.body);
+ }
+ if (req.headers) {
+ if (!options.headers) { options.headers = new Headers(); }
+ for (const header in req.headers) {
+ if (!req.headers.hasOwnProperty(header)) { continue; }
+ options.headers.set(header, req.headers[header]);
+ }
+ }
+ const url = req.url.startsWith('http') ?
+ req.url : this.getBaseUrl() + req.url;
+ const fetchReq = {
+ url,
+ fetchOptions: options,
+ anonymizedUrl: req.reportUrlAsIs ? url : req.anonymizedUrl,
+ };
+ const xhr = this.fetch(fetchReq).then(response => {
+ if (!response.ok) {
+ if (req.errFn) {
+ return req.errFn.call(undefined, response);
+ }
+ this.fire('server-error', {request: fetchReq, response});
+ }
+ return response;
+ }).catch(err => {
+ this.fire('network-error', {error: err});
+ if (req.errFn) {
+ return req.errFn.call(undefined, null, err);
+ } else {
+ throw err;
+ }
+ });
+
+ if (req.parseResponse) {
+ return xhr.then(res => this.getResponseObject(res));
+ }
+
+ return xhr;
+ }
+
+ checkCredentials() {
+ if (this._credentialCheck.checking) {
+ return;
+ }
+ this._credentialCheck.checking = true;
+ let req = {url: '/accounts/self/detail', reportUrlAsIs: true};
+ req = this.addAcceptJsonHeader(req);
+ // Skip the REST response cache.
+ return this.fetchRawJSON(req).then(res => {
+ if (!res) { return; }
+ if (res.status === 403) {
+ this.fire('auth-error');
+ this._cache.delete('/accounts/self/detail');
+ } else if (res.ok) {
+ return this.getResponseObject(res);
+ }
+ }).then(res => {
+ this._credentialCheck.checking = false;
+ if (res) {
+ this._cache.set('/accounts/self/detail', res);
+ }
+ return res;
+ }).catch(err => {
+ this._credentialCheck.checking = false;
+ if (err && err.message === FAILED_TO_FETCH_ERROR) {
+ this.fire('auth-error');
+ this._cache.delete('/accounts/self/detail');
+ }
+ });
+ }
+
+ /**
+ * @param {string} prefix
+ */
+ invalidateFetchPromisesPrefix(prefix) {
+ this._fetchPromisesCache.invalidatePrefix(prefix);
+ this._cache.invalidatePrefix(prefix);
+ }
+ }
+
+ window.SiteBasedCache = SiteBasedCache;
+ window.FetchPromisesCache = FetchPromisesCache;
+ window.GrRestApiHelper = GrRestApiHelper;
+})(window);
+
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html
new file mode 100644
index 0000000..4eaf1bc
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html
@@ -0,0 +1,177 @@
+<!DOCTYPE html>
+<!--
+@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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-rest-api-helper</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../../test/common-test-setup.html"/>
+<script src="../../../../scripts/util.js"></script>
+<script src="../gr-auth.js"></script>
+<script src="gr-rest-api-helper.js"></script>
+
+<script>void(0);</script>
+
+<script>
+ suite('gr-rest-api-helper tests', () => {
+ let helper;
+ let sandbox;
+ let cache;
+ let fetchPromisesCache;
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ cache = new SiteBasedCache();
+ fetchPromisesCache = new FetchPromisesCache();
+ const credentialCheck = {checking: false};
+
+ window.CANONICAL_PATH = 'testhelper';
+
+ const mockRestApiInterface = {
+ getBaseUrl: sinon.stub().returns(window.CANONICAL_PATH),
+ fire: sinon.stub(),
+ };
+
+ const testJSON = ')]}\'\n{"hello": "bonjour"}';
+ sandbox.stub(window, 'fetch').returns(Promise.resolve({
+ ok: true,
+ text() {
+ return Promise.resolve(testJSON);
+ },
+ }));
+
+ helper = new GrRestApiHelper(cache, Gerrit.Auth, fetchPromisesCache,
+ credentialCheck, mockRestApiInterface);
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ });
+
+ suite('fetchJSON()', () => {
+ test('Sets header to accept application/json', () => {
+ const authFetchStub = sandbox.stub(helper._auth, 'fetch')
+ .returns(Promise.resolve());
+ helper.fetchJSON({url: '/dummy/url'});
+ assert.isTrue(authFetchStub.called);
+ assert.equal(authFetchStub.lastCall.args[1].headers.get('Accept'),
+ 'application/json');
+ });
+
+ test('Use header option accept when provided', () => {
+ const authFetchStub = sandbox.stub(helper._auth, 'fetch')
+ .returns(Promise.resolve());
+ const headers = new Headers();
+ headers.append('Accept', '*/*');
+ const fetchOptions = {headers};
+ helper.fetchJSON({url: '/dummy/url', fetchOptions});
+ assert.isTrue(authFetchStub.called);
+ assert.equal(authFetchStub.lastCall.args[1].headers.get('Accept'),
+ '*/*');
+ });
+ });
+
+ test('JSON prefix is properly removed', done => {
+ helper.fetchJSON({url: '/dummy/url'}).then(obj => {
+ assert.deepEqual(obj, {hello: 'bonjour'});
+ done();
+ });
+ });
+
+ test('cached results', done => {
+ let n = 0;
+ sandbox.stub(helper, 'fetchJSON', () => {
+ return Promise.resolve(++n);
+ });
+ const promises = [];
+ promises.push(helper.fetchCacheURL('/foo'));
+ promises.push(helper.fetchCacheURL('/foo'));
+ promises.push(helper.fetchCacheURL('/foo'));
+
+ Promise.all(promises).then(results => {
+ assert.deepEqual(results, [1, 1, 1]);
+ helper.fetchCacheURL('/foo').then(foo => {
+ assert.equal(foo, 1);
+ done();
+ });
+ });
+ });
+
+ test('cached promise', done => {
+ const promise = Promise.reject(new Error('foo'));
+ cache.set('/foo', promise);
+ helper.fetchCacheURL({url: '/foo'}).catch(p => {
+ assert.equal(p.message, 'foo');
+ done();
+ });
+ });
+
+ test('cache invalidation', () => {
+ cache.set('/foo/bar', 1);
+ cache.set('/bar', 2);
+ fetchPromisesCache.set('/foo/bar', 3);
+ fetchPromisesCache.set('/bar', 4);
+ helper.invalidateFetchPromisesPrefix('/foo/');
+ assert.isFalse(cache.has('/foo/bar'));
+ assert.isTrue(cache.has('/bar'));
+ assert.isUndefined(fetchPromisesCache.get('/foo/bar'));
+ assert.strictEqual(4, fetchPromisesCache.get('/bar'));
+ });
+
+ test('params are properly encoded', () => {
+ let url = helper.urlWithParams('/path/', {
+ sp: 'hola',
+ gr: 'guten tag',
+ noval: null,
+ });
+ assert.equal(url,
+ window.CANONICAL_PATH + '/path/?sp=hola&gr=guten%20tag&noval');
+
+ url = helper.urlWithParams('/path/', {
+ sp: 'hola',
+ en: ['hey', 'hi'],
+ });
+ assert.equal(url, window.CANONICAL_PATH + '/path/?sp=hola&en=hey&en=hi');
+
+ // Order must be maintained with array params.
+ url = helper.urlWithParams('/path/', {
+ l: ['c', 'b', 'a'],
+ });
+ assert.equal(url, window.CANONICAL_PATH + '/path/?l=c&l=b&l=a');
+ });
+
+ test('request callbacks can be canceled', done => {
+ let cancelCalled = false;
+ window.fetch.returns(Promise.resolve({
+ body: {
+ cancel() { cancelCalled = true; },
+ },
+ }));
+ const cancelCondition = () => { return true; };
+ helper.fetchJSON({url: '/dummy/url', cancelCondition}).then(
+ obj => {
+ assert.isUndefined(obj);
+ assert.isTrue(cancelCalled);
+ done();
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-select/gr-select.js b/polygerrit-ui/app/elements/shared/gr-select/gr-select.js
index 357ab40..ecf542f 100644
--- a/polygerrit-ui/app/elements/shared/gr-select/gr-select.js
+++ b/polygerrit-ui/app/elements/shared/gr-select/gr-select.js
@@ -60,6 +60,10 @@
this.bindValue = this.nativeSelect.value;
},
+ focus() {
+ this.nativeSelect.focus();
+ },
+
ready() {
// If not set via the property, set bind-value to the element value.
if (this.bindValue == undefined) {
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html
index 131dc79..fa54dba 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html
@@ -55,6 +55,9 @@
iron-autogrow-textarea {
padding: 2px;
position: relative;
+
+ /** This is needed for firefox */
+ --iron-autogrow-textarea_-_white-space: pre-wrap;
}
#textarea.noBorder {
border: none;
diff --git a/polygerrit-ui/app/elements/test/plugin.html b/polygerrit-ui/app/elements/test/plugin.html
index a0d7467..ecd9007 100644
--- a/polygerrit-ui/app/elements/test/plugin.html
+++ b/polygerrit-ui/app/elements/test/plugin.html
@@ -1,8 +1,10 @@
<dom-module id="my-plugin">
<script>
- Gerrit.install(plugin =>
- plugin.registerStyleModule('app-theme', 'myplugin-app-theme')
- );
+ Gerrit.install(plugin => {
+ plugin.registerStyleModule('app-theme', 'myplugin-app-theme');
+ plugin.registerStyleModule('app-theme-light', 'myplugin-app-theme-light');
+ plugin.registerStyleModule('app-theme-dark', 'myplugin-app-theme-dark');
+ });
</script>
</dom-module>
@@ -11,6 +13,15 @@
<style>
html {
--primary-text-color: #F00BAA;
+ }
+ </style>
+ </template>
+</dom-module>
+
+<dom-module id="myplugin-app-theme-light">
+ <template>
+ <style>
+ html {
--header-background-color: #F01BAA;
--header-title-content: "MyGerrit";
--footer-background-color: #F02BAA;
@@ -18,3 +29,16 @@
</style>
</template>
</dom-module>
+
+<dom-module id="myplugin-app-theme-dark">
+ <template>
+ <style>
+ html {
+ --primary-text-color: red;
+ --header-background-color: black;
+ --header-title-content: "MyGerrit Dark";
+ --footer-background-color: yellow;
+ }
+ </style>
+ </template>
+</dom-module>
diff --git a/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils.js b/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils.js
new file mode 100644
index 0000000..238cf15
--- /dev/null
+++ b/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils.js
@@ -0,0 +1,55 @@
+(function(window) {
+ 'use strict';
+
+ if (window.GrDisplayNameUtils) {
+ return;
+ }
+
+ const ANONYMOUS_NAME = 'Anonymous';
+
+ class GrDisplayNameUtils {
+ /**
+ * enableEmail when true enables to fallback to using email if
+ * the account name is not avilable.
+ */
+ static getUserName(config, account, enableEmail) {
+ if (account && account.name) {
+ return account.name;
+ } else if (account && account.username) {
+ return account.username;
+ } else if (enableEmail && account && account.email) {
+ return account.email;
+ } else if (config && config.user &&
+ config.user.anonymous_coward_name !== 'Anonymous Coward') {
+ return config.user.anonymous_coward_name;
+ }
+
+ return ANONYMOUS_NAME;
+ }
+
+ static getAccountDisplayName(config, account, enableEmail) {
+ const reviewerName = this._accountOrAnon(config, account, enableEmail);
+ const reviewerEmail = this._accountEmail(account.email);
+ const reviewerStatus = account.status ? '(' + account.status + ')' : '';
+ return [reviewerName, reviewerEmail, reviewerStatus]
+ .filter(p => p.length > 0).join(' ');
+ }
+
+ static _accountOrAnon(config, reviewer, enableEmail) {
+ return this.getUserName(config, reviewer, !!enableEmail);
+ }
+
+ static _accountEmail(email) {
+ if (typeof email !== 'undefined') {
+ return '<' + email + '>';
+ }
+ return '';
+ }
+
+ static getGroupDisplayName(group) {
+ return group.name + ' (group)';
+ }
+ }
+
+ window.GrDisplayNameUtils = GrDisplayNameUtils;
+})(window);
diff --git a/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils_test.html b/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils_test.html
new file mode 100644
index 0000000..25ca4c5
--- /dev/null
+++ b/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils_test.html
@@ -0,0 +1,140 @@
+<!DOCTYPE html>
+<!--
+@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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-display-name-utils</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../test/common-test-setup.html"/>
+<script src="gr-display-name-utils.js"></script>
+
+<script>
+ suite('gr-display-name-utils tests', () => {
+ // eslint-disable-next-line no-unused-vars
+ const config = {
+ user: {
+ anonymous_coward_name: 'Anonymous Coward',
+ },
+ };
+
+
+ test('getUserName name only', () => {
+ const account = {
+ name: 'test-name',
+ };
+ assert.deepEqual(GrDisplayNameUtils.getUserName(config, account, true),
+ 'test-name');
+ });
+
+ test('getUserName username only', () => {
+ const account = {
+ username: 'test-user',
+ };
+ assert.deepEqual(GrDisplayNameUtils.getUserName(config, account, true),
+ 'test-user');
+ });
+
+ test('getUserName email only', () => {
+ const account = {
+ email: 'test-user@test-url.com',
+ };
+ assert.deepEqual(GrDisplayNameUtils.getUserName(config, account, true),
+ 'test-user@test-url.com');
+ });
+
+ test('getUserName returns not Anonymous Coward as the anon name', () => {
+ assert.deepEqual(GrDisplayNameUtils.getUserName(config, null, true),
+ 'Anonymous');
+ });
+
+ test('getUserName for the config returning the anon name', () => {
+ const config = {
+ user: {
+ anonymous_coward_name: 'Test Anon',
+ },
+ };
+ assert.deepEqual(GrDisplayNameUtils.getUserName(config, null, true),
+ 'Test Anon');
+ });
+
+ test('getAccountDisplayName - account with name only', () => {
+ assert.equal(
+ GrDisplayNameUtils.getAccountDisplayName(config,
+ {name: 'Some user name'}),
+ 'Some user name');
+ });
+
+ test('getAccountDisplayName - account with email only', () => {
+ assert.equal(
+ GrDisplayNameUtils.getAccountDisplayName(config,
+ {email: 'my@example.com'}),
+ 'Anonymous <my@example.com>');
+ });
+
+ test('getAccountDisplayName - account with email only - allowEmail', () => {
+ assert.equal(
+ GrDisplayNameUtils.getAccountDisplayName(config,
+ {email: 'my@example.com'}, true),
+ 'my@example.com <my@example.com>');
+ });
+
+ test('getAccountDisplayName - account with name and status', () => {
+ assert.equal(
+ GrDisplayNameUtils.getAccountDisplayName(config, {
+ name: 'Some name',
+ status: 'OOO',
+ }),
+ 'Some name (OOO)');
+ });
+
+ test('getAccountDisplayName - account with name and email', () => {
+ assert.equal(
+ GrDisplayNameUtils.getAccountDisplayName(config, {
+ name: 'Some name',
+ email: 'my@example.com',
+ }),
+ 'Some name <my@example.com>');
+ });
+
+ test('getAccountDisplayName - account with name, email and status', () => {
+ assert.equal(
+ GrDisplayNameUtils.getAccountDisplayName(config, {
+ name: 'Some name',
+ email: 'my@example.com',
+ status: 'OOO',
+ }),
+ 'Some name <my@example.com> (OOO)');
+ });
+
+ test('getGroupDisplayName', () => {
+ assert.equal(
+ GrDisplayNameUtils.getGroupDisplayName({name: 'Some user name'}),
+ 'Some user name (group)');
+ });
+
+ test('_accountEmail', () => {
+ assert.equal(
+ GrDisplayNameUtils._accountEmail('email@gerritreview.com'),
+ '<email@gerritreview.com>');
+ assert.equal(GrDisplayNameUtils._accountEmail(undefined), '');
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider.js b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider.js
new file mode 100644
index 0000000..67001d2
--- /dev/null
+++ b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider.js
@@ -0,0 +1,46 @@
+/**
+ * @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.
+ */
+(function(window) {
+ 'use strict';
+
+ if (window.GrEmailSuggestionsProvider) {
+ return;
+ }
+
+ class GrEmailSuggestionsProvider {
+ constructor(restAPI) {
+ this._restAPI = restAPI;
+ }
+
+ getSuggestions(input) {
+ return this._restAPI.getSuggestedAccounts(`${input}`)
+ .then(accounts => {
+ if (!accounts) { return []; }
+ return accounts;
+ });
+ }
+
+ makeSuggestionItem(account) {
+ return {
+ name: GrDisplayNameUtils.getAccountDisplayName(null, account, true),
+ value: {account, count: 1},
+ };
+ }
+ }
+
+ window.GrEmailSuggestionsProvider = GrEmailSuggestionsProvider;
+})(window);
diff --git a/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html
new file mode 100644
index 0000000..fb6b5d4
--- /dev/null
+++ b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<!--
+@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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-email-suggestions-provider</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="../../elements/shared/gr-rest-api-interface/gr-rest-api-interface.html"/>
+<script src="../gr-display-name-utils/gr-display-name-utils.js"></script>
+<script src="gr-email-suggestions-provider.js"></script>
+
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ </template>
+</test-fixture>
+
+<script>
+ suite('GrEmailSuggestionsProvider tests', () => {
+ let sandbox;
+ let restAPI;
+ let provider;
+ const account1 = {
+ name: 'Some name',
+ email: 'some@example.com',
+ };
+ const account2 = {
+ email: 'other@example.com',
+ _account_id: 3,
+ };
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+
+ stub('gr-rest-api-interface', {
+ getConfig() { return Promise.resolve({}); },
+ });
+ restAPI = fixture('basic');
+ provider = new GrEmailSuggestionsProvider(restAPI);
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ });
+
+ test('getSuggestions', done => {
+ const getSuggestedAccountsStub =
+ sandbox.stub(restAPI, 'getSuggestedAccounts')
+ .returns(Promise.resolve([account1, account2]));
+
+ provider.getSuggestions('Some input').then(res => {
+ assert.deepEqual(res, [account1, account2]);
+ assert.isTrue(getSuggestedAccountsStub.calledOnce);
+ assert.equal(getSuggestedAccountsStub.lastCall.args[0], 'Some input');
+ done();
+ });
+ });
+
+ test('makeSuggestionItem', () => {
+ assert.deepEqual(provider.makeSuggestionItem(account1), {
+ name: 'Some name <some@example.com>',
+ value: {
+ account: account1,
+ count: 1,
+ },
+ });
+
+ assert.deepEqual(provider.makeSuggestionItem(account2), {
+ name: 'other@example.com <other@example.com>',
+ value: {
+ account: account2,
+ count: 1,
+ },
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider.js b/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider.js
new file mode 100644
index 0000000..a95670b
--- /dev/null
+++ b/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider.js
@@ -0,0 +1,47 @@
+/**
+ * @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.
+ */
+(function(window) {
+ 'use strict';
+
+ if (window.GrGroupSuggestionsProvider) {
+ return;
+ }
+
+ class GrGroupSuggestionsProvider {
+ constructor(restAPI) {
+ this._restAPI = restAPI;
+ }
+
+ getSuggestions(input) {
+ return this._restAPI.getSuggestedGroups(`${input}`)
+ .then(groups => {
+ if (!groups) { return []; }
+ const keys = Object.keys(groups);
+ return keys.map(key => {
+ return Object.assign({}, groups[key], {name: key});
+ });
+ });
+ }
+
+ makeSuggestionItem(suggestion) {
+ return {name: suggestion.name,
+ value: {group: {name: suggestion.name, id: suggestion.id}}};
+ }
+ }
+
+ window.GrGroupSuggestionsProvider = GrGroupSuggestionsProvider;
+})(window);
diff --git a/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.html b/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.html
new file mode 100644
index 0000000..b60aaa9
--- /dev/null
+++ b/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<!--
+@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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-group-suggestions-provider</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="../../elements/shared/gr-rest-api-interface/gr-rest-api-interface.html"/>
+<script src="../gr-display-name-utils/gr-display-name-utils.js"></script>
+<script src="gr-group-suggestions-provider.js"></script>
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ </template>
+</test-fixture>
+
+<script>
+ suite('GrGroupSuggestionsProvider tests', () => {
+ let sandbox;
+ let restAPI;
+ let provider;
+ const group1 = {
+ name: 'Some name',
+ id: 1,
+ };
+ const group2 = {
+ name: 'Other name',
+ id: 3,
+ url: 'abcd',
+ };
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+
+ stub('gr-rest-api-interface', {
+ getConfig() { return Promise.resolve({}); },
+ });
+ restAPI = fixture('basic');
+ provider = new GrGroupSuggestionsProvider(restAPI);
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ });
+
+ test('getSuggestions', done => {
+ const getSuggestedAccountsStub =
+ sandbox.stub(restAPI, 'getSuggestedGroups')
+ .returns(Promise.resolve({
+ 'Some name': {id: 1},
+ 'Other name': {id: 3, url: 'abcd'},
+ }));
+
+ provider.getSuggestions('Some input').then(res => {
+ assert.deepEqual(res, [group1, group2]);
+ assert.isTrue(getSuggestedAccountsStub.calledOnce);
+ assert.equal(getSuggestedAccountsStub.lastCall.args[0], 'Some input');
+ done();
+ });
+ });
+
+ test('makeSuggestionItem', () => {
+ assert.deepEqual(provider.makeSuggestionItem(group1), {
+ name: 'Some name',
+ value: {
+ group: {
+ name: 'Some name',
+ id: 1,
+ },
+ },
+ });
+
+ assert.deepEqual(provider.makeSuggestionItem(group2), {
+ name: 'Other name',
+ value: {
+ group: {
+ name: 'Other name',
+ id: 3,
+ },
+ },
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.js b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.js
new file mode 100644
index 0000000..7f1a9b1
--- /dev/null
+++ b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.js
@@ -0,0 +1,90 @@
+/**
+ * @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.
+ */
+(function(window) {
+ 'use strict';
+
+ if (window.GrReviewerSuggestionsProvider) {
+ return;
+ }
+
+ class GrReviewerSuggestionsProvider {
+ constructor(restAPI, changeNumber, allowAnyUser) {
+ this._changeNumber = changeNumber;
+ this._allowAnyUser = allowAnyUser;
+ this._restAPI = restAPI;
+ }
+
+ init() {
+ if (this._initPromise) {
+ return this._initPromise;
+ }
+ const getConfigPromise = this._restAPI.getConfig().then(cfg => {
+ this._config = cfg;
+ });
+ const getLoggedInPromise = this._restAPI.getLoggedIn().then(loggedIn => {
+ this._loggedIn = loggedIn;
+ });
+ this._initPromise = Promise.all([getConfigPromise, getLoggedInPromise])
+ .then(() => {
+ this._initialized = true;
+ });
+ return this._initPromise;
+ }
+
+ getSuggestions(input) {
+ if (!this._initialized || !this._loggedIn) {
+ return Promise.resolve([]);
+ }
+ const api = this._restAPI;
+ const xhr = this._allowAnyUser ?
+ api.getSuggestedAccounts(`cansee:${this._changeNumber} ${input}`) :
+ api.getChangeSuggestedReviewers(this._changeNumber, input);
+
+ return xhr.then(reviewers => (reviewers || []));
+ }
+
+ makeSuggestionItem(suggestion) {
+ if (suggestion.account) {
+ // Reviewer is an account suggestion from getChangeSuggestedReviewers.
+ return {
+ name: GrDisplayNameUtils.getAccountDisplayName(this._config,
+ suggestion.account, false),
+ value: suggestion,
+ };
+ }
+
+ if (suggestion.group) {
+ // Reviewer is a group suggestion from getChangeSuggestedReviewers.
+ return {
+ name: GrDisplayNameUtils.getGroupDisplayName(suggestion.group),
+ value: suggestion,
+ };
+ }
+
+ if (suggestion._account_id) {
+ // Reviewer is an account suggestion from getSuggestedAccounts.
+ return {
+ name: GrDisplayNameUtils.getAccountDisplayName(this._config,
+ suggestion, false),
+ value: {account: suggestion, count: 1},
+ };
+ }
+ }
+ }
+
+ window.GrReviewerSuggestionsProvider = GrReviewerSuggestionsProvider;
+})(window);
diff --git a/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.html b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.html
new file mode 100644
index 0000000..bb73520
--- /dev/null
+++ b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.html
@@ -0,0 +1,260 @@
+<!DOCTYPE html>
+<!--
+@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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-reviewer-suggestions-provider</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<script src="/bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="../../elements/shared/gr-rest-api-interface/gr-rest-api-interface.html"/>
+<script src="../gr-display-name-utils/gr-display-name-utils.js"></script>
+<script src="gr-reviewer-suggestions-provider.js"></script>
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ </template>
+</test-fixture>
+
+<script>
+ suite('GrReviewerSuggestionsProvider tests', () => {
+ let sandbox;
+ let _nextAccountId = 0;
+ const makeAccount = function(opt_status) {
+ const accountId = ++_nextAccountId;
+ return {
+ _account_id: accountId,
+ name: 'name ' + accountId,
+ email: 'email ' + accountId,
+ status: opt_status,
+ };
+ };
+ let _nextAccountId2 = 0;
+ const makeAccount2 = function(opt_status) {
+ const accountId2 = ++_nextAccountId2;
+ return {
+ _account_id: accountId2,
+ name: 'name ' + accountId2,
+ status: opt_status,
+ };
+ };
+
+ let owner;
+ let existingReviewer1;
+ let existingReviewer2;
+ let suggestion1;
+ let suggestion2;
+ let suggestion3;
+ let restAPI;
+ let provider;
+
+ let redundantSuggestion1;
+ let redundantSuggestion2;
+ let redundantSuggestion3;
+ let change;
+
+ setup(done => {
+ owner = makeAccount();
+ existingReviewer1 = makeAccount();
+ existingReviewer2 = makeAccount();
+ suggestion1 = {account: makeAccount()};
+ suggestion2 = {account: makeAccount()};
+ suggestion3 = {
+ group: {
+ id: 'suggested group id',
+ name: 'suggested group',
+ },
+ };
+
+ stub('gr-rest-api-interface', {
+ getLoggedIn() { return Promise.resolve(true); },
+ getConfig() { return Promise.resolve({}); },
+ });
+
+ restAPI = fixture('basic');
+ change = {
+ _number: 42,
+ owner,
+ reviewers: {
+ CC: [existingReviewer1],
+ REVIEWER: [existingReviewer2],
+ },
+ };
+ sandbox = sinon.sandbox.create();
+ return flush(done);
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ });
+ suite('allowAnyUser set to false', () => {
+ setup(done => {
+ provider = new GrReviewerSuggestionsProvider(restAPI, change._number,
+ false);
+ provider.init().then(done);
+ });
+ suite('stubbed values for _getReviewerSuggestions', () => {
+ setup(() => {
+ stub('gr-rest-api-interface', {
+ getChangeSuggestedReviewers() {
+ redundantSuggestion1 = {account: existingReviewer1};
+ redundantSuggestion2 = {account: existingReviewer2};
+ redundantSuggestion3 = {account: owner};
+ return Promise.resolve([redundantSuggestion1, redundantSuggestion2,
+ redundantSuggestion3, suggestion1, suggestion2, suggestion3]);
+ },
+ });
+ });
+
+ test('makeSuggestionItem formats account or group accordingly', () => {
+ let account = makeAccount();
+ const account3 = makeAccount2();
+ let suggestion = provider.makeSuggestionItem({account});
+ assert.deepEqual(suggestion, {
+ name: account.name + ' <' + account.email + '>',
+ value: {account},
+ });
+
+ const group = {name: 'test'};
+ suggestion = provider.makeSuggestionItem({group});
+ assert.deepEqual(suggestion, {
+ name: group.name + ' (group)',
+ value: {group},
+ });
+
+ suggestion = provider.makeSuggestionItem(account);
+ assert.deepEqual(suggestion, {
+ name: account.name + ' <' + account.email + '>',
+ value: {account, count: 1},
+ });
+
+ suggestion = provider.makeSuggestionItem({account: {}});
+ assert.deepEqual(suggestion, {
+ name: 'Anonymous',
+ value: {account: {}},
+ });
+
+ provider._config = {
+ user: {
+ anonymous_coward_name: 'Anonymous Coward Name',
+ },
+ };
+
+ suggestion = provider.makeSuggestionItem({account: {}});
+ assert.deepEqual(suggestion, {
+ name: 'Anonymous Coward Name',
+ value: {account: {}},
+ });
+
+ account = makeAccount('OOO');
+
+ suggestion = provider.makeSuggestionItem({account});
+ assert.deepEqual(suggestion, {
+ name: account.name + ' <' + account.email + '> (OOO)',
+ value: {account},
+ });
+
+ suggestion = provider.makeSuggestionItem(account);
+ assert.deepEqual(suggestion, {
+ name: account.name + ' <' + account.email + '> (OOO)',
+ value: {account, count: 1},
+ });
+
+ sandbox.stub(GrDisplayNameUtils, '_accountEmail',
+ () => {
+ return '';
+ });
+
+ suggestion = provider.makeSuggestionItem(account3);
+ assert.deepEqual(suggestion, {
+ name: account3.name,
+ value: {account: account3, count: 1},
+ });
+ });
+
+ test('getSuggestions', done => {
+ provider.getSuggestions().then(reviewers => {
+ // Default is no filtering.
+ assert.equal(reviewers.length, 6);
+ assert.deepEqual(reviewers,
+ [redundantSuggestion1, redundantSuggestion2,
+ redundantSuggestion3, suggestion1, suggestion2, suggestion3]);
+ }).then(done);
+ });
+
+ test('getSuggestions short circuits when logged out', () => {
+ // API call is already stubbed.
+ const xhrSpy = restAPI.getChangeSuggestedReviewers;
+ provider._loggedIn = false;
+ return provider.getSuggestions('').then(() => {
+ assert.isFalse(xhrSpy.called);
+ provider._loggedIn = true;
+ return provider.getSuggestions('').then(() => {
+ assert.isTrue(xhrSpy.called);
+ });
+ });
+ });
+ });
+
+ test('getChangeSuggestedReviewers is used', done => {
+ const suggestReviewerStub =
+ sandbox.stub(restAPI, 'getChangeSuggestedReviewers')
+ .returns(Promise.resolve([]));
+ const suggestAccountStub =
+ sandbox.stub(restAPI, 'getSuggestedAccounts')
+ .returns(Promise.resolve([]));
+
+ provider.getSuggestions('').then(() => {
+ assert.isTrue(suggestReviewerStub.calledOnce);
+ assert.isTrue(suggestReviewerStub.calledWith(42, ''));
+ assert.isFalse(suggestAccountStub.called);
+ done();
+ });
+ });
+ });
+
+ suite('allowAnyUser set to true', () => {
+ setup(done => {
+ provider = new GrReviewerSuggestionsProvider(restAPI, change._number,
+ true);
+ provider.init().then(done);
+ });
+
+ test('getSuggestedAccounts is used', done => {
+ const suggestReviewerStub =
+ sandbox.stub(restAPI, 'getChangeSuggestedReviewers')
+ .returns(Promise.resolve([]));
+ const suggestAccountStub =
+ sandbox.stub(restAPI, 'getSuggestedAccounts')
+ .returns(Promise.resolve([]));
+
+ provider.getSuggestions('').then(() => {
+ assert.isFalse(suggestReviewerStub.called);
+ assert.isTrue(suggestAccountStub.calledOnce);
+ assert.isTrue(suggestAccountStub.calledWith('cansee:42 '));
+ done();
+ });
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/template_test_srcs/template_test.js b/polygerrit-ui/app/template_test_srcs/template_test.js
index 3de6227..ec3b7d5 100644
--- a/polygerrit-ui/app/template_test_srcs/template_test.js
+++ b/polygerrit-ui/app/template_test_srcs/template_test.js
@@ -35,6 +35,11 @@
'GrReviewerUpdatesParser',
'GrCountStringFormatter',
'GrThemeApi',
+ 'SiteBasedCache',
+ 'FetchPromisesCache',
+ 'GrRestApiHelper',
+ 'GrDisplayNameUtils',
+ 'GrReviewerSuggestionsProvider',
'moment',
'page',
'util',
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index 0fbc8f1..4754cd8 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -23,6 +23,7 @@
<script src="/bower_components/web-component-tester/browser.js"></script>
<script>
const testFiles = [];
+ const scriptsPath = '../scripts/';
const elementsPath = '../elements/';
const behaviorsPath = '../behaviors/';
@@ -61,9 +62,9 @@
'change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html',
'change-list/gr-create-change-help/gr-create-change-help_test.html',
'change-list/gr-dashboard-view/gr-dashboard-view_test.html',
+ // TODO: uncomment file & fix tests. The file was missed in this list for a long time.
+ // 'change-list/gr-repo-header/gr-repo-header_test.html',
'change-list/gr-user-header/gr-user-header_test.html',
- 'change/gr-account-entry/gr-account-entry_test.html',
- 'change/gr-account-list/gr-account-list_test.html',
'change/gr-change-actions/gr-change-actions_test.html',
'change/gr-change-metadata/gr-change-metadata-it_test.html',
'change/gr-change-metadata/gr-change-metadata_test.html',
@@ -105,10 +106,13 @@
'core/gr-search-bar/gr-search-bar_test.html',
'core/gr-smart-search/gr-smart-search_test.html',
'diff/gr-comment-api/gr-comment-api_test.html',
+ 'diff/gr-coverage-layer/gr-coverage-layer_test.html',
'diff/gr-diff-builder/gr-diff-builder_test.html',
'diff/gr-diff-cursor/gr-diff-cursor_test.html',
'diff/gr-diff-highlight/gr-annotation_test.html',
'diff/gr-diff-highlight/gr-diff-highlight_test.html',
+ // TODO: uncomment file & fix tests. The file was missed in this list for a long time.
+ // 'diff/gr-diff-host/gr-diff-host_test.html',
'diff/gr-diff-mode-selector/gr-diff-mode-selector_test.html',
'diff/gr-diff-processor/gr-diff-processor_test.html',
'diff/gr-diff-selection/gr-diff-selection_test.html',
@@ -127,6 +131,8 @@
'plugins/gr-admin-api/gr-admin-api_test.html',
'plugins/gr-styles-api/gr-styles-api_test.html',
'plugins/gr-attribute-helper/gr-attribute-helper_test.html',
+ // TODO: uncomment file & fix tests. The file was missed in this list for a long time.
+ // 'plugins/gr-dom-hooks/gr-dom-hooks_test.html',
'plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html',
'plugins/gr-event-helper/gr-event-helper_test.html',
'plugins/gr-external-style/gr-external-style_test.html',
@@ -135,7 +141,10 @@
'plugins/gr-popup-interface/gr-popup-interface_test.html',
'plugins/gr-repo-api/gr-repo-api_test.html',
'plugins/gr-settings-api/gr-settings-api_test.html',
+ 'plugins/gr-theme-api/gr-theme-api_test.html',
'settings/gr-account-info/gr-account-info_test.html',
+ // TODO: uncomment file & fix tests. The file was missed in this list for a long time.
+ // 'settings/gr-agreements-list/gr-agreements-list_test.html',
'settings/gr-change-table-editor/gr-change-table-editor_test.html',
'settings/gr-cla-view/gr-cla-view_test.html',
'settings/gr-edit-preferences/gr-edit-preferences_test.html',
@@ -149,7 +158,9 @@
'settings/gr-settings-view/gr-settings-view_test.html',
'settings/gr-ssh-editor/gr-ssh-editor_test.html',
'settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html',
+ 'shared/gr-account-entry/gr-account-entry_test.html',
'shared/gr-account-label/gr-account-label_test.html',
+ 'shared/gr-account-list/gr-account-list_test.html',
'shared/gr-account-link/gr-account-link_test.html',
'shared/gr-alert/gr-alert_test.html',
'shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html',
@@ -161,33 +172,48 @@
'shared/gr-comment-thread/gr-comment-thread_test.html',
'shared/gr-comment/gr-comment_test.html',
'shared/gr-copy-clipboard/gr-copy-clipboard_test.html',
+ 'shared/gr-count-string-formatter/gr-count-string-formatter_test.html',
'shared/gr-cursor-manager/gr-cursor-manager_test.html',
'shared/gr-date-formatter/gr-date-formatter_test.html',
'shared/gr-dialog/gr-dialog_test.html',
'shared/gr-diff-preferences/gr-diff-preferences_test.html',
'shared/gr-download-commands/gr-download-commands_test.html',
+ 'shared/gr-dropdown/gr-dropdown_test.html',
'shared/gr-dropdown-list/gr-dropdown-list_test.html',
'shared/gr-editable-content/gr-editable-content_test.html',
'shared/gr-editable-label/gr-editable-label_test.html',
'shared/gr-formatted-text/gr-formatted-text_test.html',
+ 'shared/gr-hovercard/gr-hovercard_test.html',
+ 'shared/gr-js-api-interface/gr-annotation-actions-context_test.html',
+ 'shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html',
'shared/gr-js-api-interface/gr-change-actions-js-api_test.html',
'shared/gr-js-api-interface/gr-change-reply-js-api_test.html',
'shared/gr-js-api-interface/gr-js-api-interface_test.html',
+ // TODO: uncomment file & fix tests. The file was missed in this list for a long time.
+ // 'shared/gr-js-api-interface/gr-plugin-action-context_test.html',
'shared/gr-js-api-interface/gr-plugin-endpoints_test.html',
'shared/gr-js-api-interface/gr-plugin-rest-api_test.html',
'shared/gr-fixed-panel/gr-fixed-panel_test.html',
'shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html',
+ // TODO: uncomment file & fix tests. The file was missed in this list for a long time.
+ // 'shared/gr-label-info/gr-label-info_test.html',
'shared/gr-lib-loader/gr-lib-loader_test.html',
'shared/gr-limited-text/gr-limited-text_test.html',
'shared/gr-linked-chip/gr-linked-chip_test.html',
'shared/gr-linked-text/gr-linked-text_test.html',
'shared/gr-list-view/gr-list-view_test.html',
+ 'shared/gr-overlay/gr-overlay_test.html',
'shared/gr-page-nav/gr-page-nav_test.html',
'shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html',
'shared/gr-rest-api-interface/gr-auth_test.html',
+ 'shared/gr-rest-api-interface/gr-etag-decorator_test.html',
'shared/gr-rest-api-interface/gr-rest-api-interface_test.html',
'shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html',
+ 'shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html',
+ // TODO: uncomment file & fix tests. The file was missed in this list for a long time.
+ // 'shared/gr-rest-api-interface/mock-diff-response_test.html',
'shared/gr-select/gr-select_test.html',
+ 'shared/gr-shell-command/gr-shell-command_test.html',
'shared/gr-storage/gr-storage_test.html',
'shared/gr-textarea/gr-textarea_test.html',
'shared/gr-tooltip-content/gr-tooltip-content_test.html',
@@ -211,8 +237,10 @@
'rest-client-behavior/rest-client-behavior_test.html',
'gr-access-behavior/gr-access-behavior_test.html',
'gr-admin-nav-behavior/gr-admin-nav-behavior_test.html',
- 'gr-anonymous-name-behavior/gr-anonymous-name-behavior_test.html',
'gr-change-table-behavior/gr-change-table-behavior_test.html',
+ // TODO: uncomment file & fix tests. The file was missed in this list for a long time.
+ // 'gr-list-view-behavior/gr-list-view-behavior_test.html',
+ 'gr-display-name-behavior/gr-display-name-behavior_test.html',
'gr-patch-set-behavior/gr-patch-set-behavior_test.html',
'gr-path-list-behavior/gr-path-list-behavior_test.html',
'gr-tooltip-behavior/gr-tooltip-behavior_test.html',
@@ -226,5 +254,17 @@
testFiles.push(file);
}
+ const scripts = [
+ 'gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.html',
+ 'gr-group-suggestions-provider/gr-group-suggestions-provider_test.html',
+ 'gr-display-name-utils/gr-display-name-utils_test.html',
+ 'gr-email-suggestions-provider/gr-email-suggestions-provider_test.html',
+ ];
+ /* eslint-enable max-len */
+ for (let file of scripts) {
+ file = scriptsPath + file;
+ testFiles.push(file);
+ }
+
WCT.loadSuites(testFiles);
</script>
diff --git a/proto/BUILD b/proto/BUILD
index cef28a1..57be265 100644
--- a/proto/BUILD
+++ b/proto/BUILD
@@ -1,3 +1,6 @@
+load("@rules_java//java:defs.bzl", "java_proto_library")
+load("@rules_proto//proto:defs.bzl", "proto_library")
+
proto_library(
name = "cache_proto",
srcs = ["cache.proto"],
diff --git a/proto/testing/BUILD b/proto/testing/BUILD
index b9032cf..f32d745 100644
--- a/proto/testing/BUILD
+++ b/proto/testing/BUILD
@@ -1,3 +1,6 @@
+load("@rules_java//java:defs.bzl", "java_proto_library")
+load("@rules_proto//proto:defs.bzl", "proto_library")
+
proto_library(
name = "test_proto",
testonly = 1,
diff --git a/resources/BUILD b/resources/BUILD
index 18d8df6..b53ae4c 100644
--- a/resources/BUILD
+++ b/resources/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_import")
load("//tools/bzl:genrule2.bzl", "genrule2")
java_import(
diff --git a/tools/BUILD b/tools/BUILD
index 3d0959b2..29626d9 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -3,6 +3,8 @@
"JDK9_JVM_OPTS",
"default_java_toolchain",
)
+load("@rules_java//java:defs.bzl", "java_package_configuration")
+load("@rules_python//python:defs.bzl", "py_binary")
exports_files(["nongoogle.bzl"])
diff --git a/tools/bzl/bazelisk_version.bzl b/tools/bzl/bazelisk_version.bzl
new file mode 100644
index 0000000..d8b3d10
--- /dev/null
+++ b/tools/bzl/bazelisk_version.bzl
@@ -0,0 +1,16 @@
+_template = """
+load("@bazel_skylib//lib:versions.bzl", "versions")
+
+def check_bazel_version():
+ versions.check(minimum_bazel_version = "{version}")
+""".strip()
+
+def _impl(repository_ctx):
+ repository_ctx.symlink(Label("@//:.bazelversion"), ".bazelversion")
+ bazelversion = repository_ctx.read(".bazelversion").strip()
+
+ repository_ctx.file("BUILD", executable = False)
+
+ repository_ctx.file("check.bzl", executable = False, content = _template.format(version = bazelversion))
+
+bazelisk_version = repository_rule(implementation = _impl)
diff --git a/tools/bzl/classpath.bzl b/tools/bzl/classpath.bzl
index 0d43be7..3be7a12 100644
--- a/tools/bzl/classpath.bzl
+++ b/tools/bzl/classpath.bzl
@@ -1,10 +1,10 @@
def _classpath_collector(ctx):
all = []
for d in ctx.attr.deps:
- if hasattr(d, "java"):
- all.append(d.java.transitive_runtime_deps)
- if hasattr(d.java.compilation_info, "runtime_classpath"):
- all.append(d.java.compilation_info.runtime_classpath)
+ if JavaInfo in d:
+ all.append(d[JavaInfo].transitive_runtime_deps)
+ if hasattr(d[JavaInfo].compilation_info, "runtime_classpath"):
+ all.append(d[JavaInfo].compilation_info.runtime_classpath)
elif hasattr(d, "files"):
all.append(d.files)
diff --git a/tools/bzl/java.bzl b/tools/bzl/java.bzl
index 7c41fbe..8996b69 100644
--- a/tools/bzl/java.bzl
+++ b/tools/bzl/java.bzl
@@ -15,11 +15,13 @@
# Syntactic sugar for native java_library() rule:
# accept exported_deps attributes
+load("@rules_java//java:defs.bzl", "java_library")
+
def java_library2(deps = [], exported_deps = [], exports = [], **kwargs):
if exported_deps:
deps = deps + exported_deps
exports = exports + exported_deps
- native.java_library(
+ java_library(
deps = deps,
exports = exports,
**kwargs
diff --git a/tools/bzl/javadoc.bzl b/tools/bzl/javadoc.bzl
index 754bd96..77c2d4a 100644
--- a/tools/bzl/javadoc.bzl
+++ b/tools/bzl/javadoc.bzl
@@ -17,8 +17,11 @@
def _impl(ctx):
zip_output = ctx.outputs.zip
- transitive_jars = depset(transitive = [j.java.transitive_deps for j in ctx.attr.libs])
- source_jars = depset(transitive = [j.java.source_jars for j in ctx.attr.libs])
+ transitive_jars = depset(transitive = [j[JavaInfo].transitive_deps for j in ctx.attr.libs])
+
+ # TODO(davido): Remove list to depset conversion on source_jars, when this issue is fixed:
+ # https://github.com/bazelbuild/bazel/issues/4221
+ source_jars = depset(transitive = [depset(j[JavaInfo].source_jars) for j in ctx.attr.libs])
transitive_jar_paths = [j.path for j in transitive_jars.to_list()]
dir = ctx.outputs.zip.path + ".dir"
diff --git a/tools/bzl/junit.bzl b/tools/bzl/junit.bzl
index 1a30997..1cf82ea 100644
--- a/tools/bzl/junit.bzl
+++ b/tools/bzl/junit.bzl
@@ -18,6 +18,8 @@
# See https://github.com/bazelbuild/bazel/issues/1017 for background.
+load("@rules_java//java:defs.bzl", "java_test")
+
_OUTPUT = """import org.junit.runners.Suite;
import org.junit.runner.RunWith;
@@ -55,7 +57,7 @@
ctx.attr.outname,
))
-_GenSuite = rule(
+_gen_suite = rule(
attrs = {
"srcs": attr.label_list(allow_files = True),
"outname": attr.string(),
@@ -73,7 +75,7 @@
def junit_tests(name, srcs, **kwargs):
s_name = name.replace("-", "_") + "TestSuite"
- _GenSuite(
+ _gen_suite(
name = s_name,
srcs = srcs,
outname = s_name,
@@ -84,7 +86,7 @@
"//:java_next": POST_JDK8_OPTS,
"//conditions:default": [],
})
- native.java_test(
+ java_test(
name = name,
test_class = s_name,
srcs = srcs + [":" + s_name],
diff --git a/tools/bzl/maven.bzl b/tools/bzl/maven.bzl
index 71aa91c..36e3084e 100644
--- a/tools/bzl/maven.bzl
+++ b/tools/bzl/maven.bzl
@@ -14,6 +14,8 @@
# Merge maven files
+load("@rules_java//java:defs.bzl", "java_import")
+
def cmd(jars):
return ("$(location //tools:merge_jars) $@ " +
" ".join(["$(location %s)" % j for j in jars]))
@@ -25,7 +27,7 @@
tools = srcs + ["//tools:merge_jars"],
outs = ["%s__merged.jar" % name],
)
- native.java_import(
+ java_import(
name = name,
jars = [":%s__merged_bin" % name],
**kwargs
diff --git a/tools/bzl/pkg_war.bzl b/tools/bzl/pkg_war.bzl
index 90dc607..ab753bd 100644
--- a/tools/bzl/pkg_war.bzl
+++ b/tools/bzl/pkg_war.bzl
@@ -102,8 +102,8 @@
transitive_context_libs = []
if ctx.attr.context:
for jar in ctx.attr.context:
- if hasattr(jar, "java"):
- transitive_context_libs.append(jar.java.transitive_runtime_deps)
+ if JavaInfo in jar:
+ transitive_context_libs.append(jar[JavaInfo].transitive_runtime_deps)
elif hasattr(jar, "files"):
transitive_context_libs.append(jar.files)
diff --git a/tools/bzl/plugin.bzl b/tools/bzl/plugin.bzl
index 066fe43..ed64d1b 100644
--- a/tools/bzl/plugin.bzl
+++ b/tools/bzl/plugin.bzl
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_binary", "java_library")
load("//tools/bzl:genrule2.bzl", "genrule2")
PLUGIN_DEPS = ["//plugins:plugin-lib"]
@@ -21,7 +22,7 @@
dir_name = None,
target_suffix = "",
**kwargs):
- native.java_library(
+ java_library(
name = name + "__plugin",
srcs = srcs,
resources = resources,
@@ -35,7 +36,7 @@
if not dir_name:
dir_name = name
- native.java_binary(
+ java_binary(
name = "%s__non_stamped" % name,
deploy_manifest_lines = manifest_entries + ["Gerrit-ApiType: plugin"],
main_class = "Dummy",
diff --git a/tools/eclipse/BUILD b/tools/eclipse/BUILD
index 814a56f..e091fc1 100644
--- a/tools/eclipse/BUILD
+++ b/tools/eclipse/BUILD
@@ -1,3 +1,4 @@
+load("@rules_java//java:defs.bzl", "java_library")
load("//tools/bzl:classpath.bzl", "classpath_collector")
load("//tools/bzl:pkg_war.bzl", "LIBS", "PGMLIBS")
load(