Merge "Merge branch 'stable-3.1'"
This commit is contained in:
@@ -139,11 +139,12 @@ Use a file system supporting snapshots to keep the period where the gerrit
|
|||||||
server is read-only or down as short as possible.
|
server is read-only or down as short as possible.
|
||||||
|
|
||||||
[#cons-backup-read-only]
|
[#cons-backup-read-only]
|
||||||
=== Turn master read-only for backup
|
=== Turn primary server read-only for backup
|
||||||
|
|
||||||
Make the server read-only before taking the backup. This means read-access
|
Make the primary server handling write operations read-only before taking the
|
||||||
is still available during backup, because only write operations have to be
|
backup. This means read-access is still available from replica servers during
|
||||||
stopped to ensure consistency. This can be implemented using the
|
backup, because only write operations have to be stopped to ensure consistency.
|
||||||
|
This can be implemented using the
|
||||||
link:https://gerrit.googlesource.com/plugins/readonly/[_readonly_,role=external,window=_blank] plugin.
|
link:https://gerrit.googlesource.com/plugins/readonly/[_readonly_,role=external,window=_blank] plugin.
|
||||||
|
|
||||||
[#cons-backup-replicate]
|
[#cons-backup-replicate]
|
||||||
@@ -180,11 +181,12 @@ If you are using Gerrit replica to offload read traffic you can use one of these
|
|||||||
replica for creating backups.
|
replica for creating backups.
|
||||||
|
|
||||||
[#cons-backup-offline]
|
[#cons-backup-offline]
|
||||||
=== Take master offline for backup
|
=== Take primary server offline for backup
|
||||||
|
|
||||||
Shutdown the server before taking a backup. This is simple but means downtime
|
Shut down the primary server handling write operations before taking a backup.
|
||||||
for the users. Also crons and currently running cron jobs (e.g. repacking
|
This is simple but means downtime for the users. Also crons and currently
|
||||||
repositories) which affect the repositories may need to be shut down.
|
running cron jobs (e.g. repacking repositories) which affect the repositories
|
||||||
|
may need to be shut down.
|
||||||
|
|
||||||
[#backup-methods]
|
[#backup-methods]
|
||||||
== Backup methods
|
== Backup methods
|
||||||
|
|||||||
@@ -386,10 +386,10 @@ stored in a local H2 database, but there is an extension point that
|
|||||||
allows to plug in alternate implementations for storing the reviewed
|
allows to plug in alternate implementations for storing the reviewed
|
||||||
flags. To replace the storage for reviewed flags a plugin needs to
|
flags. To replace the storage for reviewed flags a plugin needs to
|
||||||
implement the link:dev-plugins.html#account-patch-review-store[
|
implement the link:dev-plugins.html#account-patch-review-store[
|
||||||
AccountPatchReviewStore] interface. E.g. to support a multi-master
|
AccountPatchReviewStore] interface. E.g. to support a cluster setup with
|
||||||
setup where reviewed flags should be replicated between the master
|
multiple primary servers handling write operations where reviewed flags should
|
||||||
nodes one could implement a store for the reviewed flags that is
|
be replicated between the primary nodes one could implement a store for the
|
||||||
based on MySQL with replication.
|
reviewed flags that is based on MySQL with replication.
|
||||||
|
|
||||||
[[account-sequence]]
|
[[account-sequence]]
|
||||||
== Account Sequence
|
== Account Sequence
|
||||||
|
|||||||
@@ -826,7 +826,8 @@ changes for up to 1024 projects can be held in the cache.
|
|||||||
+
|
+
|
||||||
Default value is 0 (disabled). It is disabled by default due to the fact
|
Default value is 0 (disabled). It is disabled by default due to the fact
|
||||||
that change updates are not communicated between Gerrit servers. Hence
|
that change updates are not communicated between Gerrit servers. Hence
|
||||||
this cache should be disabled in an multi-master/multi-replica setup.
|
this cache should be disabled in a cluster setup using multiple primary
|
||||||
|
or multiple replica nodes.
|
||||||
+
|
+
|
||||||
The cache should be flushed whenever the database changes table is modified
|
The cache should be flushed whenever the database changes table is modified
|
||||||
outside of Gerrit.
|
outside of Gerrit.
|
||||||
@@ -1575,7 +1576,8 @@ options.
|
|||||||
+
|
+
|
||||||
Used on Gerrit replica installations. If set to true the Gerrit JVM is
|
Used on Gerrit replica installations. If set to true the Gerrit JVM is
|
||||||
called with the '--replica' switch, enabling replica mode. If no value is
|
called with the '--replica' switch, enabling replica mode. If no value is
|
||||||
set (or any other value), Gerrit defaults to master mode.
|
set (or any other value), Gerrit defaults to primary mode enabling write
|
||||||
|
operations.
|
||||||
|
|
||||||
[[container.slave]]container.slave::
|
[[container.slave]]container.slave::
|
||||||
+
|
+
|
||||||
@@ -4442,7 +4444,7 @@ SSH-compression since git does not compress the ref announcement during
|
|||||||
handshake.
|
handshake.
|
||||||
+
|
+
|
||||||
Compression can be especially useful when Gerrit replicas are being used
|
Compression can be especially useful when Gerrit replicas are being used
|
||||||
for the larger clones and fetches and the master server mostly takes
|
for the larger clones and fetches and the primary server mostly takes
|
||||||
small receive-packs.
|
small receive-packs.
|
||||||
+
|
+
|
||||||
By default, `false`.
|
By default, `false`.
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ Group] reindex the affected groups manually.
|
|||||||
|
|
||||||
== Replication
|
== Replication
|
||||||
|
|
||||||
In a replicated setting (eg. backups and or master/replica
|
In a replicated setting (eg. backups and or primary/replica configurations), all
|
||||||
configurations), all refs in the `All-Users` project must be copied
|
refs in the `All-Users` project on primary nodes must be copied onto all
|
||||||
onto all replicas, including `refs/groups/*`, `refs/meta/group-names`
|
replicas, including `refs/groups/*`, `refs/meta/group-names` and
|
||||||
and `refs/sequences/groups`.
|
`refs/sequences/groups`.
|
||||||
|
|||||||
@@ -795,8 +795,8 @@ Configuration,role=external,window=_blank]
|
|||||||
|
|
||||||
This plugin replaces the built-in Gerrit H2 based websession cache with
|
This plugin replaces the built-in Gerrit H2 based websession cache with
|
||||||
a flatfile based implementation. This implementation is shareable
|
a flatfile based implementation. This implementation is shareable
|
||||||
among multiple Gerrit servers, making it useful for multi-master
|
among multiple Gerrit servers, making it useful for cluster
|
||||||
Gerrit installations.
|
Gerrit installations having multiple primary Gerrit nodes.
|
||||||
|
|
||||||
link:https://gerrit-review.googlesource.com/admin/repos/plugins/websession-flatfile[
|
link:https://gerrit-review.googlesource.com/admin/repos/plugins/websession-flatfile[
|
||||||
Project,role=external,window=_blank] |
|
Project,role=external,window=_blank] |
|
||||||
|
|||||||
@@ -2306,7 +2306,8 @@ flags is growing without bound. The store must be able handle this data
|
|||||||
volume efficiently.
|
volume efficiently.
|
||||||
|
|
||||||
Gerrit implements this extension point, but plugins may bind another
|
Gerrit implements this extension point, but plugins may bind another
|
||||||
implementation, e.g. one that supports multi-master.
|
implementation, e.g. one that supports cluster setup with multiple
|
||||||
|
primary Gerrit nodes handling write operations.
|
||||||
|
|
||||||
----
|
----
|
||||||
DynamicItem.bind(binder(), AccountPatchReviewStore.class)
|
DynamicItem.bind(binder(), AccountPatchReviewStore.class)
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ external log cleaning service to clean up the prior logs.
|
|||||||
|
|
||||||
== KNOWN ISSUES
|
== KNOWN ISSUES
|
||||||
Replica daemon caches can quickly become out of date when modifications
|
Replica daemon caches can quickly become out of date when modifications
|
||||||
are made on the master. The following configuration is suggested in
|
are made on the primary node. The following configuration is suggested in
|
||||||
a replica to reduce the maxAge for each cache entry, so that changes
|
a replica to reduce the maxAge for each cache entry, so that changes
|
||||||
are recognized in a reasonable period of time:
|
are recognized in a reasonable period of time:
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ and if LDAP support was enabled, also include:
|
|||||||
maxAge = 5 min
|
maxAge = 5 min
|
||||||
----
|
----
|
||||||
|
|
||||||
Automatic cache coherency between master and replica systems is
|
Automatic cache coherency between primary and replica systems is
|
||||||
planned to be implemented in a future version.
|
planned to be implemented in a future version.
|
||||||
|
|
||||||
GERRIT
|
GERRIT
|
||||||
|
|||||||
@@ -15,10 +15,10 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
# --------------------------------------------------------
|
# --------------------------------------------------------
|
||||||
# Install this hook script as post-receive hook in replicated repositories
|
# Install this hook script as post-receive hook in replicated repositories
|
||||||
# hosted by a gerrit slave which are updated by push replication from the
|
# hosted by a gerrit replica which are updated by push replication from the
|
||||||
# corresponding gerrit master.
|
# corresponding gerrit primary node.
|
||||||
#
|
#
|
||||||
# In the gerrit master configure the replication plugin to push changes from
|
# In the gerrit primary node configure the replication plugin to push changes from
|
||||||
# refs/changes/ to refs/tmp/changes/
|
# refs/changes/ to refs/tmp/changes/
|
||||||
# remote.NAME.push = +refs/changes/*:refs/tmp/changes/*
|
# remote.NAME.push = +refs/changes/*:refs/tmp/changes/*
|
||||||
# remote.NAME.push = +refs/heads/*:refs/heads/*
|
# remote.NAME.push = +refs/heads/*:refs/heads/*
|
||||||
@@ -26,26 +26,26 @@
|
|||||||
# And if it's a Gerrit mirror:
|
# And if it's a Gerrit mirror:
|
||||||
# remote.NAME.push = +refs/meta/*:refs/meta/*
|
# remote.NAME.push = +refs/meta/*:refs/meta/*
|
||||||
#
|
#
|
||||||
# In the replicated repository in the gerrit slave configure
|
# In the replicated repository in the gerrit replica configure
|
||||||
# receive.hideRefs = refs/changes/
|
# receive.hideRefs = refs/changes/
|
||||||
# in order to not advertise the big number of refs in this namespace when
|
# in order to not advertise the big number of refs in this namespace when
|
||||||
# the gerrit master's replication plugin is pushing a change
|
# the gerrit primary's replication plugin is pushing a change
|
||||||
#
|
#
|
||||||
# Whenever a ref under refs/tmp/changes/ is arriving this hook will move it
|
# Whenever a ref under refs/tmp/changes/ is arriving this hook will move it
|
||||||
# to refs/changes/. This helps to avoid the large overhead of advertising all
|
# to refs/changes/. This helps to avoid the large overhead of advertising all
|
||||||
# refs/changes/ refs to the gerrit master when it replicates changes to the
|
# refs/changes/ refs to the gerrit primary when it replicates changes to the
|
||||||
# slave..
|
# replica.
|
||||||
#
|
#
|
||||||
# Make this script executable then link to it in the repository you would like
|
# Make this script executable then link to it in the repository you would like
|
||||||
# to use it in.
|
# to use it in.
|
||||||
# cd /path/to/your/repository.git
|
# cd /path/to/your/repository.git
|
||||||
# ln -sf <shared hooks directory>/post-receive-move-tmp-refs hooks/post-receive
|
# ln -sf <shared hooks directory>/post-receive-move-tmp-refs hooks/post-receive
|
||||||
#
|
#
|
||||||
# If you want to use this by default for repositories on the Gerrit slave you
|
# If you want to use this by default for repositories on the Gerrit replica you
|
||||||
# can set up a git template directory $TEMPLATE_DIR/hooks/post-receive and
|
# can set up a git template directory $TEMPLATE_DIR/hooks/post-receive and
|
||||||
# configure init.templateDir in the ~/.gitconfig of the user that receives the
|
# configure init.templateDir in the ~/.gitconfig of the user that receives the
|
||||||
# replication on the mirror host. That way when a new repository is created on
|
# replication on the mirror host. That way when a new repository is created on
|
||||||
# the master and hence on the mirror (if configured that way) it will
|
# the primary and hence on the mirror (if configured that way) it will
|
||||||
# automatically have the "tmp-refs" commit hook installed.
|
# automatically have the "tmp-refs" commit hook installed.
|
||||||
# See https://git-scm.com/docs/git-init#_template_directory for details.
|
# See https://git-scm.com/docs/git-init#_template_directory for details.
|
||||||
|
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ import java.util.Optional;
|
|||||||
/**
|
/**
|
||||||
* Allows implementors to control how certain exceptions should be handled.
|
* Allows implementors to control how certain exceptions should be handled.
|
||||||
*
|
*
|
||||||
* <p>This interface is intended to be implemented for multi-master setups to control the behavior
|
* <p>This interface is intended to be implemented for cluster setups with multiple primary nodes to
|
||||||
* for handling exceptions that are thrown by a lower layer that handles the consensus and
|
* control the behavior for handling exceptions that are thrown by a lower layer that handles the
|
||||||
* synchronization between different server nodes. E.g. if an operation fails because consensus for
|
* consensus and synchronization between different server nodes. E.g. if an operation fails because
|
||||||
* a Git update could not be achieved (e.g. due to slow responding server nodes) this interface can
|
* consensus for a Git update could not be achieved (e.g. due to slow responding server nodes) this
|
||||||
* be used to retry the request instead of failing it immediately.
|
* interface can be used to retry the request instead of failing it immediately.
|
||||||
*/
|
*/
|
||||||
@ExtensionPoint
|
@ExtensionPoint
|
||||||
public interface ExceptionHook {
|
public interface ExceptionHook {
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ import java.util.Optional;
|
|||||||
* number of reviewed flags is growing without bound. The store must be able handle this data volume
|
* number of reviewed flags is growing without bound. The store must be able handle this data volume
|
||||||
* efficiently.
|
* efficiently.
|
||||||
*
|
*
|
||||||
* <p>For a multi-master setup the store must replicate the data between the masters.
|
* <p>For a cluster setups with multiple primary nodes the store must replicate the data between the
|
||||||
|
* primary servers.
|
||||||
*/
|
*/
|
||||||
public interface AccountPatchReviewStore {
|
public interface AccountPatchReviewStore {
|
||||||
|
|
||||||
|
|||||||
@@ -820,9 +820,9 @@ public class MergeOp implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The patch set ref is not found but we want to merge the change. We can't safely do that
|
// The patch set ref is not found but we want to merge the change. We can't safely do that
|
||||||
// if the patch set ref is missing. In a multi-master setup this can indicate a replication
|
// if the patch set ref is missing. In a cluster setups with multiple primary nodes this can
|
||||||
// lag (e.g. the change meta data was already replicated, but the replication of the patch
|
// indicate a replication lag (e.g. the change meta data was already replicated, but the
|
||||||
// set ref is still pending).
|
// replication of the patch set ref is still pending).
|
||||||
commitStatus.logProblem(
|
commitStatus.logProblem(
|
||||||
changeId,
|
changeId,
|
||||||
"Patch set ref "
|
"Patch set ref "
|
||||||
|
|||||||
@@ -638,10 +638,9 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
|
|||||||
// However, if there are CPU in abundance and the server is reachable through
|
// However, if there are CPU in abundance and the server is reachable through
|
||||||
// slow networks, gits with huge amount of refs can benefit from SSH-compression
|
// slow networks, gits with huge amount of refs can benefit from SSH-compression
|
||||||
// since git does not compress the ref announcement during the handshake.
|
// since git does not compress the ref announcement during the handshake.
|
||||||
//
|
// Compression can be especially useful when Gerrit replica are being used
|
||||||
// Compression can be especially useful when Gerrit slaves are being used
|
// for the larger clones and fetches and the primary server handling write
|
||||||
// for the larger clones and fetches and the master server mostly takes small
|
// operations mostly takes small receive-packs.
|
||||||
// receive-packs.
|
|
||||||
|
|
||||||
if (enableCompression) {
|
if (enableCompression) {
|
||||||
compressionFactories.add(BuiltinCompressions.zlib);
|
compressionFactories.add(BuiltinCompressions.zlib);
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ public class ProjectWatchIT extends AbstractDaemonTest {
|
|||||||
requestScopeOperations.setApiUser(user.id());
|
requestScopeOperations.setApiUser(user.id());
|
||||||
|
|
||||||
// watch keyword in project as user
|
// watch keyword in project as user
|
||||||
watch(watchedProject, "multimaster");
|
watch(watchedProject, "multiprimary");
|
||||||
|
|
||||||
// push a change with keyword -> should trigger email notification
|
// push a change with keyword -> should trigger email notification
|
||||||
requestScopeOperations.setApiUser(admin.id());
|
requestScopeOperations.setApiUser(admin.id());
|
||||||
@@ -314,7 +314,7 @@ public class ProjectWatchIT extends AbstractDaemonTest {
|
|||||||
cloneProject(Project.nameKey(watchedProject), admin);
|
cloneProject(Project.nameKey(watchedProject), admin);
|
||||||
PushOneCommit.Result r =
|
PushOneCommit.Result r =
|
||||||
pushFactory
|
pushFactory
|
||||||
.create(admin.newIdent(), watchedRepo, "Document multimaster setup", "a.txt", "a1")
|
.create(admin.newIdent(), watchedRepo, "Document multiprimary setup", "a.txt", "a1")
|
||||||
.to("refs/for/master");
|
.to("refs/for/master");
|
||||||
r.assertOkStatus();
|
r.assertOkStatus();
|
||||||
|
|
||||||
@@ -323,7 +323,7 @@ public class ProjectWatchIT extends AbstractDaemonTest {
|
|||||||
assertThat(messages).hasSize(1);
|
assertThat(messages).hasSize(1);
|
||||||
Message m = messages.get(0);
|
Message m = messages.get(0);
|
||||||
assertThat(m.rcpt()).containsExactly(user.getEmailAddress());
|
assertThat(m.rcpt()).containsExactly(user.getEmailAddress());
|
||||||
assertThat(m.body()).contains("Change subject: Document multimaster setup\n");
|
assertThat(m.body()).contains("Change subject: Document multiprimary setup\n");
|
||||||
assertThat(m.body()).contains("Gerrit-PatchSet: 1\n");
|
assertThat(m.body()).contains("Gerrit-PatchSet: 1\n");
|
||||||
sender.clear();
|
sender.clear();
|
||||||
|
|
||||||
@@ -418,7 +418,7 @@ public class ProjectWatchIT extends AbstractDaemonTest {
|
|||||||
requestScopeOperations.setApiUser(user.id());
|
requestScopeOperations.setApiUser(user.id());
|
||||||
|
|
||||||
// watch keyword in project as user
|
// watch keyword in project as user
|
||||||
watch(allProjects.get(), "multimaster");
|
watch(allProjects.get(), "multiprimary");
|
||||||
|
|
||||||
// push a change with keyword to any project -> should trigger email
|
// push a change with keyword to any project -> should trigger email
|
||||||
// notification
|
// notification
|
||||||
@@ -426,7 +426,7 @@ public class ProjectWatchIT extends AbstractDaemonTest {
|
|||||||
TestRepository<InMemoryRepository> anyRepo = cloneProject(Project.nameKey(anyProject), admin);
|
TestRepository<InMemoryRepository> anyRepo = cloneProject(Project.nameKey(anyProject), admin);
|
||||||
PushOneCommit.Result r =
|
PushOneCommit.Result r =
|
||||||
pushFactory
|
pushFactory
|
||||||
.create(admin.newIdent(), anyRepo, "Document multimaster setup", "a.txt", "a1")
|
.create(admin.newIdent(), anyRepo, "Document multiprimary setup", "a.txt", "a1")
|
||||||
.to("refs/for/master");
|
.to("refs/for/master");
|
||||||
r.assertOkStatus();
|
r.assertOkStatus();
|
||||||
|
|
||||||
@@ -435,7 +435,7 @@ public class ProjectWatchIT extends AbstractDaemonTest {
|
|||||||
assertThat(messages).hasSize(1);
|
assertThat(messages).hasSize(1);
|
||||||
Message m = messages.get(0);
|
Message m = messages.get(0);
|
||||||
assertThat(m.rcpt()).containsExactly(user.getEmailAddress());
|
assertThat(m.rcpt()).containsExactly(user.getEmailAddress());
|
||||||
assertThat(m.body()).contains("Change subject: Document multimaster setup\n");
|
assertThat(m.body()).contains("Change subject: Document multiprimary setup\n");
|
||||||
assertThat(m.body()).contains("Gerrit-PatchSet: 1\n");
|
assertThat(m.body()).contains("Gerrit-PatchSet: 1\n");
|
||||||
sender.clear();
|
sender.clear();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
/**
|
||||||
|
* @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';
|
||||||
|
|
||||||
|
// Avoid duplicate registeration
|
||||||
|
if (window.EventEmitter) return;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An lite implementation of
|
||||||
|
* https://nodejs.org/api/events.html#events_class_eventemitter.
|
||||||
|
*
|
||||||
|
* This is unrelated to the native DOM events, you should use it when you want
|
||||||
|
* to enable EventEmitter interface on any class.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* class YourClass extends EventEmitter {
|
||||||
|
* // now all instance of YourClass will have this EventEmitter interface
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class EventEmitter {
|
||||||
|
constructor() {
|
||||||
|
/**
|
||||||
|
* Shared events map from name to the listeners.
|
||||||
|
* @type {!Object<string, Array<eventCallback>>}
|
||||||
|
*/
|
||||||
|
this._listenersMap = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register an event listener to an event.
|
||||||
|
*
|
||||||
|
* @param {string} eventName
|
||||||
|
* @param {eventCallback} cb
|
||||||
|
* @returns {Function} Unsubscribe method
|
||||||
|
*/
|
||||||
|
addListener(eventName, cb) {
|
||||||
|
if (!eventName || !cb) {
|
||||||
|
console.warn('A valid eventname and callback is required!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const listeners = this._listenersMap.get(eventName) || [];
|
||||||
|
listeners.push(cb);
|
||||||
|
this._listenersMap.set(eventName, listeners);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
this.off(eventName, cb);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alias for addListener.
|
||||||
|
on(eventName, cb) {
|
||||||
|
return this.addListener(eventName, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach event handler only once. Automatically removed.
|
||||||
|
once(eventName, cb) {
|
||||||
|
const onceWrapper = (...args) => {
|
||||||
|
cb(...args);
|
||||||
|
this.off(eventName, onceWrapper);
|
||||||
|
};
|
||||||
|
return this.on(eventName, onceWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* De-register an event listener to an event.
|
||||||
|
*
|
||||||
|
* @param {string} eventName
|
||||||
|
* @param {eventCallback} cb
|
||||||
|
*/
|
||||||
|
removeListener(eventName, cb) {
|
||||||
|
let listeners = this._listenersMap.get(eventName) || [];
|
||||||
|
listeners = listeners.filter(listener => listener !== cb);
|
||||||
|
this._listenersMap.set(eventName, listeners);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alias to removeListener
|
||||||
|
off(eventName, cb) {
|
||||||
|
this.removeListener(eventName, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronously calls each of the listeners registered for
|
||||||
|
* the event named eventName, in the order they were registered,
|
||||||
|
* passing the supplied detail to each.
|
||||||
|
*
|
||||||
|
* Returns true if the event had listeners, false otherwise.
|
||||||
|
*
|
||||||
|
* @param {string} eventName
|
||||||
|
* @param {*} detail
|
||||||
|
*/
|
||||||
|
emit(eventName, detail) {
|
||||||
|
const listeners = this._listenersMap.get(eventName) || [];
|
||||||
|
for (const listener of listeners) {
|
||||||
|
try {
|
||||||
|
listener(detail);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return listeners.length !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alias to emit.
|
||||||
|
dispatch(eventName, detail) {
|
||||||
|
return this.emit(eventName, detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove listeners for a specific event or all.
|
||||||
|
*
|
||||||
|
* @param {string} eventName if not provided, will remove all
|
||||||
|
*/
|
||||||
|
removeAllListeners(eventName) {
|
||||||
|
if (eventName) {
|
||||||
|
this._listenersMap.set(eventName, []);
|
||||||
|
} else {
|
||||||
|
this._listenersMap = new Map();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.EventEmitter = EventEmitter;
|
||||||
|
})(window);
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
<!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-api-interface</title>
|
||||||
|
|
||||||
|
<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.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-js-api-interface/gr-js-api-interface.html">
|
||||||
|
|
||||||
|
<script>void(0);</script>
|
||||||
|
|
||||||
|
<test-fixture id="basic">
|
||||||
|
<template>
|
||||||
|
<gr-js-api-interface></gr-js-api-interface>
|
||||||
|
</template>
|
||||||
|
</test-fixture>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
suite('gr-event-interface tests', () => {
|
||||||
|
let sandbox;
|
||||||
|
|
||||||
|
setup(() => {
|
||||||
|
sandbox = sinon.sandbox.create();
|
||||||
|
});
|
||||||
|
|
||||||
|
teardown(() => {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
suite('test on Gerrit', () => {
|
||||||
|
setup(() => {
|
||||||
|
fixture('basic');
|
||||||
|
Gerrit.removeAllListeners();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('communicate between plugin and Gerrit', done => {
|
||||||
|
const eventName = 'test-plugin-event';
|
||||||
|
let p;
|
||||||
|
Gerrit.on(eventName, e => {
|
||||||
|
assert.equal(e.value, 'test');
|
||||||
|
assert.equal(e.plugin, p);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
Gerrit.install(plugin => {
|
||||||
|
p = plugin;
|
||||||
|
Gerrit.emit(eventName, {value: 'test', plugin});
|
||||||
|
}, '0.1',
|
||||||
|
'http://test.com/plugins/testplugin/static/test.js');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('listen on events from core', done => {
|
||||||
|
const eventName = 'test-plugin-event';
|
||||||
|
Gerrit.on(eventName, e => {
|
||||||
|
assert.equal(e.value, 'test');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
Gerrit.emit(eventName, {value: 'test'});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('communicate across plugins', done => {
|
||||||
|
const eventName = 'test-plugin-event';
|
||||||
|
Gerrit.install(plugin => {
|
||||||
|
Gerrit.on(eventName, e => {
|
||||||
|
assert.equal(e.plugin.getPluginName(), 'testB');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}, '0.1',
|
||||||
|
'http://test.com/plugins/testA/static/testA.js');
|
||||||
|
|
||||||
|
Gerrit.install(plugin => {
|
||||||
|
Gerrit.emit(eventName, {plugin});
|
||||||
|
}, '0.1',
|
||||||
|
'http://test.com/plugins/testB/static/testB.js');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
suite('test on interfaces', () => {
|
||||||
|
let testObj;
|
||||||
|
|
||||||
|
class TestClass extends EventEmitter {
|
||||||
|
}
|
||||||
|
|
||||||
|
setup(() => {
|
||||||
|
testObj = new TestClass();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('on', () => {
|
||||||
|
const cbStub = sinon.stub();
|
||||||
|
testObj.on('test', cbStub);
|
||||||
|
testObj.emit('test');
|
||||||
|
testObj.emit('test');
|
||||||
|
assert.isTrue(cbStub.calledTwice);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('once', () => {
|
||||||
|
const cbStub = sinon.stub();
|
||||||
|
testObj.once('test', cbStub);
|
||||||
|
testObj.emit('test');
|
||||||
|
testObj.emit('test');
|
||||||
|
assert.isTrue(cbStub.calledOnce);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unsubscribe', () => {
|
||||||
|
const cbStub = sinon.stub();
|
||||||
|
const unsubscribe = testObj.on('test', cbStub);
|
||||||
|
testObj.emit('test');
|
||||||
|
unsubscribe();
|
||||||
|
testObj.emit('test');
|
||||||
|
assert.isTrue(cbStub.calledOnce);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('off', () => {
|
||||||
|
const cbStub = sinon.stub();
|
||||||
|
testObj.on('test', cbStub);
|
||||||
|
testObj.emit('test');
|
||||||
|
testObj.off('test', cbStub);
|
||||||
|
testObj.emit('test');
|
||||||
|
assert.isTrue(cbStub.calledOnce);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('removeAllListeners', () => {
|
||||||
|
const cbStub = sinon.stub();
|
||||||
|
testObj.on('test', cbStub);
|
||||||
|
testObj.removeAllListeners('test');
|
||||||
|
testObj.emit('test');
|
||||||
|
assert.isTrue(cbStub.notCalled);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -39,6 +39,7 @@ limitations under the License.
|
|||||||
3. gr-public-js-api depends on gr-plugin-rest-api
|
3. gr-public-js-api depends on gr-plugin-rest-api
|
||||||
-->
|
-->
|
||||||
<script src="gr-api-utils.js"></script>
|
<script src="gr-api-utils.js"></script>
|
||||||
|
<script src="../gr-event-interface/gr-event-interface.js"></script>
|
||||||
<script src="gr-annotation-actions-context.js"></script>
|
<script src="gr-annotation-actions-context.js"></script>
|
||||||
<script src="gr-annotation-actions-js-api.js"></script>
|
<script src="gr-annotation-actions-js-api.js"></script>
|
||||||
<script src="gr-change-actions-js-api.js"></script>
|
<script src="gr-change-actions-js-api.js"></script>
|
||||||
|
|||||||
@@ -381,4 +381,40 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
window.Plugin = Plugin;
|
window.Plugin = Plugin;
|
||||||
|
// TODO(taoalpha): List all internal supported event names.
|
||||||
|
// Also convert this to inherited class once we move Gerrit to class.
|
||||||
|
Gerrit._eventEmitter = new EventEmitter();
|
||||||
|
['addListener',
|
||||||
|
'dispatch',
|
||||||
|
'emit',
|
||||||
|
'off',
|
||||||
|
'on',
|
||||||
|
'once',
|
||||||
|
'removeAllListeners',
|
||||||
|
'removeListener',
|
||||||
|
].forEach(method => {
|
||||||
|
/**
|
||||||
|
* Enabling EventEmitter interface on Gerrit.
|
||||||
|
*
|
||||||
|
* This will enable to signal across different parts of js code without relying on DOM,
|
||||||
|
* including core to core, plugin to plugin and also core to plugin.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* // Emit this event from pluginA
|
||||||
|
* Gerrit.install(pluginA => {
|
||||||
|
* fetch("some-api").then(() => {
|
||||||
|
* Gerrit.on("your-special-event", {plugin: pluginA});
|
||||||
|
* });
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Listen on your-special-event from pluignB
|
||||||
|
* Gerrit.install(pluginB => {
|
||||||
|
* Gerrit.on("your-special-event", ({plugin}) => {
|
||||||
|
* // do something, plugin is pluginA
|
||||||
|
* });
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
Gerrit[method] = Gerrit._eventEmitter[method].bind(Gerrit._eventEmitter);
|
||||||
|
});
|
||||||
})(window);
|
})(window);
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ limitations under the License.
|
|||||||
'settings/gr-settings-view/gr-settings-view_test.html',
|
'settings/gr-settings-view/gr-settings-view_test.html',
|
||||||
'settings/gr-ssh-editor/gr-ssh-editor_test.html',
|
'settings/gr-ssh-editor/gr-ssh-editor_test.html',
|
||||||
'settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html',
|
'settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html',
|
||||||
|
'shared/gr-event-interface/gr-event-interface_test.html',
|
||||||
'shared/gr-account-entry/gr-account-entry_test.html',
|
'shared/gr-account-entry/gr-account-entry_test.html',
|
||||||
'shared/gr-account-label/gr-account-label_test.html',
|
'shared/gr-account-label/gr-account-label_test.html',
|
||||||
'shared/gr-account-list/gr-account-list_test.html',
|
'shared/gr-account-list/gr-account-list_test.html',
|
||||||
|
|||||||
Reference in New Issue
Block a user