blob: 9debc2e0ce37a79da67223bf298ffeb1c2e718bb [file] [log] [blame]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001= Gerrit Code Review - Plugin Development
Deniz Türkoglueb78b602012-05-07 14:02:36 -07002
Edwin Kempinaf275322012-07-16 11:04:01 +02003The Gerrit server functionality can be extended by installing plugins.
4This page describes how plugins for Gerrit can be developed.
5
6Depending on how tightly the extension code is coupled with the Gerrit
7server code, there is a distinction between `plugins` and `extensions`.
8
Edwin Kempinf5a77332012-07-18 11:17:53 +02009[[plugin]]
Edwin Kempin948de0f2012-07-16 10:34:35 +020010A `plugin` in Gerrit is tightly coupled code that runs in the same
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070011JVM as Gerrit. It has full access to all server internals. Plugins
12are tightly coupled to a specific major.minor server version and
13may require source code changes to compile against a different
14server version.
15
Luca Milanesio86b9b6c2017-08-09 09:54:36 +010016Plugins may require a specific major.minor.patch server version
17and may need rebuild and revalidation across different
18patch levels. A different patch level may only add new
19API interfaces and never change or extend existing ones.
20
Edwin Kempinf5a77332012-07-18 11:17:53 +020021[[extension]]
Edwin Kempin948de0f2012-07-16 10:34:35 +020022An `extension` in Gerrit runs inside of the same JVM as Gerrit
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070023in the same way as a plugin, but has limited visibility to the
Edwin Kempinfd19bfb2012-07-16 10:44:17 +020024server's internals. The limited visibility reduces the extension's
25dependencies, enabling it to be compatible across a wider range
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070026of server versions.
27
28Most of this documentation refers to either type as a plugin.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070029
Edwin Kempinf878c4b2012-07-18 09:34:25 +020030[[getting-started]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -080031== Getting started
Deniz Türkoglueb78b602012-05-07 14:02:36 -070032
David Ostrovskya052e522016-12-10 17:53:16 +010033To get started with the development of a plugin clone the sample
34plugin:
David Pursehousecf2e9002017-03-01 19:10:43 +090035
Dave Borowitz5cc8f662012-05-21 09:51:36 -070036----
David Pursehouse2cf0cb52013-08-27 16:09:53 +090037$ git clone https://gerrit.googlesource.com/plugins/cookbook-plugin
Dave Borowitz5cc8f662012-05-21 09:51:36 -070038----
David Pursehousecf2e9002017-03-01 19:10:43 +090039
40This is a project that demonstrates the various features of the
41plugin API. It can be taken as an example to develop an own plugin.
42
Edwin Kempinf878c4b2012-07-18 09:34:25 +020043When starting from this example one should take care to adapt the
David Ostrovskya052e522016-12-10 17:53:16 +010044`Gerrit-ApiVersion` in the `BUILD` to the version of Gerrit for which
45the plugin is developed.
Dave Borowitz5cc8f662012-05-21 09:51:36 -070046
Edwin Kempinf878c4b2012-07-18 09:34:25 +020047[[API]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -080048== API
Edwin Kempinf878c4b2012-07-18 09:34:25 +020049
50There are two different API formats offered against which plugins can
51be developed:
Deniz Türkoglueb78b602012-05-07 14:02:36 -070052
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070053gerrit-extension-api.jar::
54 A stable but thin interface. Suitable for extensions that need
55 to be notified of events, but do not require tight coupling to
56 the internals of Gerrit. Extensions built against this API can
57 expect to be binary compatible across a wide range of server
58 versions.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070059
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070060gerrit-plugin-api.jar::
61 The complete internals of the Gerrit server, permitting a
62 plugin to tightly couple itself and provide additional
63 functionality that is not possible as an extension. Plugins
64 built against this API are expected to break at the source
65 code level between every major.minor Gerrit release. A plugin
66 that compiles against 2.5 will probably need source code level
67 changes to work with 2.6, 2.7, and so on.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070068
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -080069== Manifest
Deniz Türkoglueb78b602012-05-07 14:02:36 -070070
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070071Plugins may provide optional description information with standard
72manifest fields:
Nasser Grainawie033b262012-05-09 17:54:21 -070073
Michael Ochmannb99feab2016-07-06 14:10:22 +020074----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070075 Implementation-Title: Example plugin showing examples
76 Implementation-Version: 1.0
77 Implementation-Vendor: Example, Inc.
Michael Ochmannb99feab2016-07-06 14:10:22 +020078----
Nasser Grainawie033b262012-05-09 17:54:21 -070079
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -080080=== ApiType
Nasser Grainawie033b262012-05-09 17:54:21 -070081
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070082Plugins using the tightly coupled `gerrit-plugin-api.jar` must
83declare this API dependency in the manifest to gain access to server
Edwin Kempin948de0f2012-07-16 10:34:35 +020084internals. If no `Gerrit-ApiType` is specified the stable `extension`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070085API will be assumed. This may cause ClassNotFoundExceptions when
86loading a plugin that needs the plugin API.
Nasser Grainawie033b262012-05-09 17:54:21 -070087
Michael Ochmannb99feab2016-07-06 14:10:22 +020088----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070089 Gerrit-ApiType: plugin
Michael Ochmannb99feab2016-07-06 14:10:22 +020090----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070091
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -080092=== Explicit Registration
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070093
94Plugins that use explicit Guice registration must name the Guice
95modules in the manifest. Up to three modules can be named in the
Edwin Kempin948de0f2012-07-16 10:34:35 +020096manifest. `Gerrit-Module` supplies bindings to the core server;
97`Gerrit-SshModule` supplies SSH commands to the SSH server (if
98enabled); `Gerrit-HttpModule` supplies servlets and filters to the HTTP
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070099server (if enabled). If no modules are named automatic registration
100will be performed by scanning all classes in the plugin JAR for
101`@Listen` and `@Export("")` annotations.
102
Michael Ochmannb99feab2016-07-06 14:10:22 +0200103----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700104 Gerrit-Module: tld.example.project.CoreModuleClassName
105 Gerrit-SshModule: tld.example.project.SshModuleClassName
106 Gerrit-HttpModule: tld.example.project.HttpModuleClassName
Michael Ochmannb99feab2016-07-06 14:10:22 +0200107----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700108
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200109[[plugin_name]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800110=== Plugin Name
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200111
David Pursehoused128c892013-10-22 21:52:21 +0900112A plugin can optionally provide its own plugin name.
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200113
Michael Ochmannb99feab2016-07-06 14:10:22 +0200114----
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200115 Gerrit-PluginName: replication
Michael Ochmannb99feab2016-07-06 14:10:22 +0200116----
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200117
118This is useful for plugins that contribute plugin-owned capabilities that
119are stored in the `project.config` file. Another use case is to be able to put
120project specific plugin configuration section in `project.config`. In this
121case it is advantageous to reserve the plugin name to access the configuration
122section in the `project.config` file.
123
124If `Gerrit-PluginName` is omitted, then the plugin's name is determined from
125the plugin file name.
126
127If a plugin provides its own name, then that plugin cannot be deployed
128multiple times under different file names on one Gerrit site.
129
130For Maven driven plugins, the following line must be included in the pom.xml
131file:
132
133[source,xml]
134----
135<manifestEntries>
136 <Gerrit-PluginName>name</Gerrit-PluginName>
137</manifestEntries>
138----
139
David Ostrovskyfdbfcad2016-11-15 06:35:29 -0800140For Bazel driven plugins, the following line must be included in the BUILD
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200141configuration file:
142
143[source,python]
144----
David Pursehouse529ec252013-09-27 13:45:14 +0900145manifest_entries = [
146 'Gerrit-PluginName: name',
147]
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200148----
149
Edwin Kempinc0b1b0e2013-10-01 14:13:54 +0200150A plugin can get its own name injected at runtime:
151
152[source,java]
153----
154public class MyClass {
155
156 private final String pluginName;
157
158 @Inject
159 public MyClass(@PluginName String pluginName) {
160 this.pluginName = pluginName;
161 }
162
David Pursehoused128c892013-10-22 21:52:21 +0900163 [...]
Edwin Kempinc0b1b0e2013-10-01 14:13:54 +0200164}
165----
166
David Pursehouse8ed0d922013-10-18 18:57:56 +0900167A plugin can get its canonical web URL injected at runtime:
168
169[source,java]
170----
171public class MyClass {
172
173 private final String url;
174
175 @Inject
176 public MyClass(@PluginCanonicalWebUrl String url) {
177 this.url = url;
178 }
179
180 [...]
181}
182----
183
184The URL is composed of the server's canonical web URL and the plugin's
185name, i.e. `http://review.example.com:8080/plugin-name`.
186
187The canonical web URL may be injected into any .jar plugin regardless of
188whether or not the plugin provides an HTTP servlet.
189
Edwin Kempinf7295742012-07-16 15:03:46 +0200190[[reload_method]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800191=== Reload Method
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700192
193If a plugin holds an exclusive resource that must be released before
194loading the plugin again (for example listening on a network port or
Edwin Kempin948de0f2012-07-16 10:34:35 +0200195acquiring a file lock) the manifest must declare `Gerrit-ReloadMode`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700196to be `restart`. Otherwise the preferred method of `reload` will
197be used, as it enables the server to hot-patch an updated plugin
198with no down time.
199
Michael Ochmannb99feab2016-07-06 14:10:22 +0200200----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700201 Gerrit-ReloadMode: restart
Michael Ochmannb99feab2016-07-06 14:10:22 +0200202----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700203
204In either mode ('restart' or 'reload') any plugin or extension can
205be updated without restarting the Gerrit server. The difference is
206how Gerrit handles the upgrade:
207
208restart::
209 The old plugin is completely stopped. All registrations of SSH
210 commands and HTTP servlets are removed. All registrations of any
211 extension points are removed. All registered LifecycleListeners
212 have their `stop()` method invoked in reverse order. The new
213 plugin is started, and registrations are made from the new
214 plugin. There is a brief window where neither the old nor the
215 new plugin is connected to the server. This means SSH commands
216 and HTTP servlets will return not found errors, and the plugin
217 will not be notified of events that occurred during the restart.
218
219reload::
220 The new plugin is started. Its LifecycleListeners are permitted
221 to perform their `start()` methods. All SSH and HTTP registrations
222 are atomically swapped out from the old plugin to the new plugin,
223 ensuring the server never returns a not found error. All extension
224 point listeners are atomically swapped out from the old plugin to
225 the new plugin, ensuring no events are missed (however some events
226 may still route to the old plugin if the swap wasn't complete yet).
227 The old plugin is stopped.
228
Edwin Kempinf7295742012-07-16 15:03:46 +0200229To reload/restart a plugin the link:cmd-plugin-reload.html[plugin reload]
230command can be used.
231
Luca Milanesio737285d2012-09-25 14:26:43 +0100232[[init_step]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800233=== Init step
Luca Milanesio737285d2012-09-25 14:26:43 +0100234
235Plugins can contribute their own "init step" during the Gerrit init
236wizard. This is useful for guiding the Gerrit administrator through
David Pursehouse659860f2013-12-16 14:50:04 +0900237the settings needed by the plugin to work properly.
Luca Milanesio737285d2012-09-25 14:26:43 +0100238
239For instance plugins to integrate Jira issues to Gerrit changes may
240contribute their own "init step" to allow configuring the Jira URL,
241credentials and possibly verify connectivity to validate them.
242
Michael Ochmannb99feab2016-07-06 14:10:22 +0200243----
Luca Milanesio737285d2012-09-25 14:26:43 +0100244 Gerrit-InitStep: tld.example.project.MyInitStep
Michael Ochmannb99feab2016-07-06 14:10:22 +0200245----
Luca Milanesio737285d2012-09-25 14:26:43 +0100246
247MyInitStep needs to follow the standard Gerrit InitStep syntax
David Pursehouse92463562013-06-24 10:16:28 +0900248and behavior: writing to the console using the injected ConsoleUI
Luca Milanesio737285d2012-09-25 14:26:43 +0100249and accessing / changing configuration settings using Section.Factory.
250
251In addition to the standard Gerrit init injections, plugins receive
252the @PluginName String injection containing their own plugin name.
253
Edwin Kempind4cfac12013-11-27 11:22:34 +0100254During their initialization plugins may get access to the
255`project.config` file of the `All-Projects` project and they are able
256to store configuration parameters in it. For this a plugin `InitStep`
Jiří Engelthaler3033a0a2015-02-16 09:44:32 +0100257can get `com.google.gerrit.pgm.init.api.AllProjectsConfig` injected:
Edwin Kempind4cfac12013-11-27 11:22:34 +0100258
259[source,java]
260----
261 public class MyInitStep implements InitStep {
262 private final String pluginName;
263 private final ConsoleUI ui;
264 private final AllProjectsConfig allProjectsConfig;
265
Doug Kelly732ad202015-11-13 13:11:32 -0800266 @Inject
Edwin Kempind4cfac12013-11-27 11:22:34 +0100267 public MyInitStep(@PluginName String pluginName, ConsoleUI ui,
268 AllProjectsConfig allProjectsConfig) {
269 this.pluginName = pluginName;
270 this.ui = ui;
271 this.allProjectsConfig = allProjectsConfig;
272 }
273
274 @Override
275 public void run() throws Exception {
Edwin Kempin93e7d5d2014-01-03 09:53:20 +0100276 }
277
278 @Override
279 public void postRun() throws Exception {
Edwin Kempind4cfac12013-11-27 11:22:34 +0100280 ui.message("\n");
281 ui.header(pluginName + " Integration");
282 boolean enabled = ui.yesno(true, "By default enabled for all projects");
Adrian Görlerd1612972014-10-20 17:06:07 +0200283 Config cfg = allProjectsConfig.load().getConfig();
Edwin Kempind4cfac12013-11-27 11:22:34 +0100284 if (enabled) {
285 cfg.setBoolean("plugin", pluginName, "enabled", enabled);
286 } else {
287 cfg.unset("plugin", pluginName, "enabled");
288 }
289 allProjectsConfig.save(pluginName, "Initialize " + pluginName + " Integration");
290 }
291 }
292----
293
Luca Milanesio737285d2012-09-25 14:26:43 +0100294Bear in mind that the Plugin's InitStep class will be loaded but
295the standard Gerrit runtime environment is not available and the plugin's
296own Guice modules were not initialized.
297This means the InitStep for a plugin is not executed in the same way that
298the plugin executes within the server, and may mean a plugin author cannot
299trivially reuse runtime code during init.
300
301For instance a plugin that wants to verify connectivity may need to statically
302call the constructor of their connection class, passing in values obtained
303from the Section.Factory rather than from an injected Config object.
304
David Pursehoused128c892013-10-22 21:52:21 +0900305Plugins' InitSteps are executed during the "Gerrit Plugin init" phase, after
306the extraction of the plugins embedded in the distribution .war file into
307`$GERRIT_SITE/plugins` and before the DB Schema initialization or upgrade.
308
309A plugin's InitStep cannot refer to Gerrit's DB Schema or any other Gerrit
310runtime objects injected at startup.
Luca Milanesio737285d2012-09-25 14:26:43 +0100311
David Pursehouse68153d72013-09-04 10:09:17 +0900312[source,java]
313----
314public class MyInitStep implements InitStep {
315 private final ConsoleUI ui;
316 private final Section.Factory sections;
317 private final String pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100318
David Pursehouse68153d72013-09-04 10:09:17 +0900319 @Inject
320 public GitBlitInitStep(final ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) {
321 this.ui = ui;
322 this.sections = sections;
323 this.pluginName = pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100324 }
David Pursehouse68153d72013-09-04 10:09:17 +0900325
326 @Override
327 public void run() throws Exception {
328 ui.header("\nMy plugin");
329
330 Section mySection = getSection("myplugin", null);
331 mySection.string("Link name", "linkname", "MyLink");
332 }
Edwin Kempin93e7d5d2014-01-03 09:53:20 +0100333
334 @Override
335 public void postRun() throws Exception {
336 }
David Pursehouse68153d72013-09-04 10:09:17 +0900337}
338----
Luca Milanesio737285d2012-09-25 14:26:43 +0100339
Edwin Kempinf5a77332012-07-18 11:17:53 +0200340[[classpath]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800341== Classpath
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700342
343Each plugin is loaded into its own ClassLoader, isolating plugins
344from each other. A plugin or extension inherits the Java runtime
345and the Gerrit API chosen by `Gerrit-ApiType` (extension or plugin)
346from the hosting server.
347
348Plugins are loaded from a single JAR file. If a plugin needs
349additional libraries, it must include those dependencies within
350its own JAR. Plugins built using Maven may be able to use the
351link:http://maven.apache.org/plugins/maven-shade-plugin/[shade plugin]
352to package additional dependencies. Relocating (or renaming) classes
353should not be necessary due to the ClassLoader isolation.
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700354
Edwin Kempin98202662013-09-18 16:03:03 +0200355[[events]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800356== Listening to Events
Edwin Kempin98202662013-09-18 16:03:03 +0200357
358Certain operations in Gerrit trigger events. Plugins may receive
359notifications of these events by implementing the corresponding
360listeners.
361
Martin Fick4c72aea2014-12-10 14:58:12 -0700362* `com.google.gerrit.common.EventListener`:
Edwin Kempin64059f52013-10-31 13:49:25 +0100363+
Hugo Arèsbc1093d2016-02-23 15:04:50 -0500364Allows to listen to events without user visibility restrictions. These
365are the same link:cmd-stream-events.html#events[events] that are also streamed by
Edwin Kempin64059f52013-10-31 13:49:25 +0100366the link:cmd-stream-events.html[gerrit stream-events] command.
367
Hugo Arèsbc1093d2016-02-23 15:04:50 -0500368* `com.google.gerrit.common.UserScopedEventListener`:
369+
370Allows to listen to events visible to the specified user. These are the
371same link:cmd-stream-events.html#events[events] that are also streamed
372by the link:cmd-stream-events.html[gerrit stream-events] command.
373
Edwin Kempin98202662013-09-18 16:03:03 +0200374* `com.google.gerrit.extensions.events.LifecycleListener`:
375+
Edwin Kempin3e7928a2013-12-03 07:39:00 +0100376Plugin start and stop
Edwin Kempin98202662013-09-18 16:03:03 +0200377
378* `com.google.gerrit.extensions.events.NewProjectCreatedListener`:
379+
380Project creation
381
382* `com.google.gerrit.extensions.events.ProjectDeletedListener`:
383+
384Project deletion
385
Edwin Kempinb27c9392013-11-19 13:12:43 +0100386* `com.google.gerrit.extensions.events.HeadUpdatedListener`:
387+
388Update of HEAD on a project
389
Stefan Lay310d77d2014-05-28 13:45:25 +0200390* `com.google.gerrit.extensions.events.UsageDataPublishedListener`:
391+
392Publication of usage data
393
Adrian Görlerf4a4c9a2014-08-22 17:09:18 +0200394* `com.google.gerrit.extensions.events.GarbageCollectorListener`:
395+
396Garbage collection ran on a project
397
Hector Oswaldo Caballero5fbbdad2015-11-11 14:30:46 -0500398* `com.google.gerrit.server.extensions.events.ChangeIndexedListener`:
399+
Hugo Arès682171f2017-04-24 13:44:43 +0200400Update of the change secondary index
401
402* `com.google.gerrit.server.extensions.events.AccountIndexedListener`:
403+
404Update of the account secondary index
Hector Oswaldo Caballero5fbbdad2015-11-11 14:30:46 -0500405
Hugo Arèsee788ddb2017-05-08 10:23:45 -0400406* `com.google.gerrit.server.extensions.events.GroupIndexedListener`:
407+
408Update of the group secondary index
409
Xin Sun97169862017-07-13 17:31:16 -0700410* `com.google.gerrit.server.extensions.events.ProjectIndexedListener`:
411+
412Update of the project secondary index
413
Luca Milanesio45da6182016-05-12 11:33:30 +0100414* `com.google.gerrit.httpd.WebLoginListener`:
415+
416User login or logout interactively on the Web user interface.
417
418The event listener is under the Gerrit http package to automatically
419inherit the javax.servlet.http dependencies and allowing to influence
420the login or logout flow with additional redirections.
421
Yang Zhenhui2659d422013-07-30 16:59:58 +0800422[[stream-events]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800423== Sending Events to the Events Stream
Yang Zhenhui2659d422013-07-30 16:59:58 +0800424
425Plugins may send events to the events stream where consumers of
426Gerrit's `stream-events` ssh command will receive them.
427
428To send an event, the plugin must invoke one of the `postEvent`
David Pursehousea9bf4762016-07-08 09:34:35 +0900429methods in the `EventDispatcher` interface, passing an instance of
Martin Fick4c72aea2014-12-10 14:58:12 -0700430its own custom event class derived from
431`com.google.gerrit.server.events.Event`.
Yang Zhenhui2659d422013-07-30 16:59:58 +0800432
David Pursehousea9bf4762016-07-08 09:34:35 +0900433[source,java]
434----
435import com.google.gerrit.common.EventDispatcher;
436import com.google.gerrit.extensions.registration.DynamicItem;
437import com.google.gwtorm.server.OrmException;
438import com.google.inject.Inject;
439
440class MyPlugin {
441 private final DynamicItem<EventDispatcher> eventDispatcher;
442
443 @Inject
444 myPlugin(DynamicItem<EventDispatcher> eventDispatcher) {
445 this.eventDispatcher = eventDispatcher;
446 }
447
448 private void postEvent(MyPluginEvent event) {
449 try {
450 eventDispatcher.get().postEvent(event);
451 } catch (OrmException e) {
452 // error handling
453 }
454 }
455}
456----
457
Martin Fick0aef6f12014-12-11 16:54:21 -0700458Plugins which define new Events should register them via the
459`com.google.gerrit.server.events.EventTypes.registerClass()`
460method. This will make the EventType known to the system.
David Pursehousea61ee502016-09-06 16:27:09 +0900461Deserializing events with the
Martin Fickf70c20a2014-12-11 17:03:15 -0700462`com.google.gerrit.server.events.EventDeserializer` class requires
463that the event be registered in EventTypes.
Martin Fick0aef6f12014-12-11 16:54:21 -0700464
Martin Fickecafc932014-12-15 14:09:41 -0700465== Modifying the Stream Event Flow
466
467It is possible to modify the stream event flow from plugins by registering
468an `com.google.gerrit.server.events.EventDispatcher`. A plugin may register
469a Dispatcher class to replace the internal Dispatcher. EventDispatcher is
470a DynamicItem, so Gerrit may only have one copy.
471
Edwin Kempin32737602014-01-23 09:04:58 +0100472[[validation]]
David Pursehouse91c5f5e2014-01-23 18:57:33 +0900473== Validation Listeners
Edwin Kempin32737602014-01-23 09:04:58 +0100474
475Certain operations in Gerrit can be validated by plugins by
476implementing the corresponding link:config-validation.html[listeners].
477
Andrii Shyshkalov6fdc8eb2016-11-29 17:45:01 +0100478[[change-message-modifier]]
479== Change Message Modifier
480
481`com.google.gerrit.server.git.ChangeMessageModifier`:
482plugins implementing this can modify commit message of the change being
483submitted by Rebase Always and Cherry Pick submit strategies as well as
484change being queried with COMMIT_FOOTERS option.
485
Saša Živkovec85a072014-01-28 10:08:25 +0100486[[receive-pack]]
487== Receive Pack Initializers
488
489Plugins may provide ReceivePack initializers which will be invoked
490by Gerrit just before a ReceivePack instance will be used. Usually,
491plugins will make use of the setXXX methods on the ReceivePack to
492set additional properties on it.
493
Saša Živkov626c7312014-02-24 17:15:08 +0100494[[post-receive-hook]]
495== Post Receive-Pack Hooks
496
497Plugins may register PostReceiveHook instances in order to get
498notified when JGit successfully receives a pack. This may be useful
499for those plugins which would like to monitor changes in Git
500repositories.
501
Hugo Arès572d5422014-06-17 14:22:03 -0400502[[pre-upload-hook]]
503== Pre Upload-Pack Hooks
504
505Plugins may register PreUploadHook instances in order to get
506notified when JGit is about to upload a pack. This may be useful
507for those plugins which would like to monitor usage in Git
508repositories.
509
Hugo Arès41b4c0d2016-08-02 15:26:57 -0400510[[post-upload-hook]]
511== Post Upload-Pack Hooks
512
513Plugins may register PostUploadHook instances in order to get notified after
514JGit is done uploading a pack.
515
Edwin Kempinf5a77332012-07-18 11:17:53 +0200516[[ssh]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800517== SSH Commands
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700518
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700519Plugins may provide commands that can be accessed through the SSH
520interface (extensions do not have this option).
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700521
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700522Command implementations must extend the base class SshCommand:
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700523
David Pursehouse68153d72013-09-04 10:09:17 +0900524[source,java]
525----
526import com.google.gerrit.sshd.SshCommand;
David Ostrovskyb7d97752013-11-09 05:23:26 +0100527import com.google.gerrit.sshd.CommandMetaData;
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700528
Ian Bulle1a12202014-02-16 17:15:42 -0800529@CommandMetaData(name="print", description="Print hello command")
David Pursehouse68153d72013-09-04 10:09:17 +0900530class PrintHello extends SshCommand {
Ian Bulle1a12202014-02-16 17:15:42 -0800531 @Override
532 protected void run() {
David Pursehouse68153d72013-09-04 10:09:17 +0900533 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700534 }
David Pursehouse68153d72013-09-04 10:09:17 +0900535}
536----
Nasser Grainawie033b262012-05-09 17:54:21 -0700537
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700538If no Guice modules are declared in the manifest, SSH commands may
Edwin Kempin948de0f2012-07-16 10:34:35 +0200539use auto-registration by providing an `@Export` annotation:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700540
David Pursehouse68153d72013-09-04 10:09:17 +0900541[source,java]
542----
543import com.google.gerrit.extensions.annotations.Export;
544import com.google.gerrit.sshd.SshCommand;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700545
David Pursehouse68153d72013-09-04 10:09:17 +0900546@Export("print")
547class PrintHello extends SshCommand {
Ian Bulle1a12202014-02-16 17:15:42 -0800548 @Override
549 protected void run() {
David Pursehouse68153d72013-09-04 10:09:17 +0900550 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700551 }
David Pursehouse68153d72013-09-04 10:09:17 +0900552}
553----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700554
555If explicit registration is being used, a Guice module must be
556supplied to register the SSH command and declared in the manifest
557with the `Gerrit-SshModule` attribute:
558
David Pursehouse68153d72013-09-04 10:09:17 +0900559[source,java]
560----
561import com.google.gerrit.sshd.PluginCommandModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700562
David Pursehouse68153d72013-09-04 10:09:17 +0900563class MyCommands extends PluginCommandModule {
Ian Bulle1a12202014-02-16 17:15:42 -0800564 @Override
David Pursehouse68153d72013-09-04 10:09:17 +0900565 protected void configureCommands() {
David Ostrovskyb7d97752013-11-09 05:23:26 +0100566 command(PrintHello.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700567 }
David Pursehouse68153d72013-09-04 10:09:17 +0900568}
569----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700570
571For a plugin installed as name `helloworld`, the command implemented
572by PrintHello class will be available to users as:
573
574----
Keunhong Parka09a6f12012-07-10 14:45:02 -0600575$ ssh -p 29418 review.example.com helloworld print
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700576----
577
David Ostrovsky79c4d892014-03-15 13:52:46 +0100578[[multiple-commands]]
579=== Multiple Commands bound to one implementation
580
David Ostrovskye3172b32013-10-13 14:19:13 +0200581Multiple SSH commands can be bound to the same implementation class. For
582example a Gerrit Shell plugin can bind different shell commands to the same
583implementation class:
584
585[source,java]
586----
587public class SshShellModule extends PluginCommandModule {
588 @Override
589 protected void configureCommands() {
590 command("ls").to(ShellCommand.class);
591 command("ps").to(ShellCommand.class);
592 [...]
593 }
594}
595----
596
597With the possible implementation:
598
599[source,java]
600----
601public class ShellCommand extends SshCommand {
602 @Override
603 protected void run() throws UnloggedFailure {
604 String cmd = getName().substring(getPluginName().length() + 1);
605 ProcessBuilder proc = new ProcessBuilder(cmd);
606 Process cmd = proc.start();
607 [...]
608 }
609}
610----
611
612And the call:
613
614----
615$ ssh -p 29418 review.example.com shell ls
616$ ssh -p 29418 review.example.com shell ps
617----
618
David Ostrovsky79c4d892014-03-15 13:52:46 +0100619[[root-level-commands]]
620=== Root Level Commands
621
David Ostrovskyb7d97752013-11-09 05:23:26 +0100622Single command plugins are also supported. In this scenario plugin binds
623SSH command to its own name. `SshModule` must inherit from
624`SingleCommandPluginModule` class:
625
626[source,java]
627----
628public class SshModule extends SingleCommandPluginModule {
629 @Override
630 protected void configure(LinkedBindingBuilder<Command> b) {
631 b.to(ShellCommand.class);
632 }
633}
634----
635
636If the plugin above is deployed under sh.jar file in `$site/plugins`
David Pursehouse659860f2013-12-16 14:50:04 +0900637directory, generic commands can be called without specifying the
David Ostrovskyb7d97752013-11-09 05:23:26 +0100638actual SSH command. Note in the example below, that the called commands
639`ls` and `ps` was not explicitly bound:
640
641----
642$ ssh -p 29418 review.example.com sh ls
643$ ssh -p 29418 review.example.com sh ps
644----
645
Martin Fick5f6222912015-11-12 14:52:50 -0700646[[search_operators]]
Edwin Kempin4b479772016-11-14 14:34:33 -0800647== Search Operators
Martin Fick5f6222912015-11-12 14:52:50 -0700648
649Plugins can define new search operators to extend change searching by
650implementing the `ChangeQueryBuilder.ChangeOperatorFactory` interface
651and registering it to an operator name in the plugin module's
652`configure()` method. The search operator name is defined during
653registration via the DynamicMap annotation mechanism. The plugin
654name will get appended to the annotated name, with an underscore
655in between, leading to the final operator name. An example
656registration looks like this:
657
658 bind(ChangeOperatorFactory.class)
659 .annotatedWith(Exports.named("sample"))
660 .to(SampleOperator.class);
661
662If this is registered in the `myplugin` plugin, then the resulting
663operator will be named `sample_myplugin`.
664
665The search operator itself is implemented by ensuring that the
666`create()` method of the class implementing the
667`ChangeQueryBuilder.ChangeOperatorFactory` interface returns a
668`Predicate<ChangeData>`. Here is a sample operator factory
David Pursehousea61ee502016-09-06 16:27:09 +0900669definition which creates a `MyPredicate`:
Martin Fick5f6222912015-11-12 14:52:50 -0700670
671[source,java]
672----
673@Singleton
674public class SampleOperator
675 implements ChangeQueryBuilder.ChangeOperatorFactory {
Edwin Kempincc82b242016-06-28 10:00:53 +0200676 public static class MyPredicate extends OperatorChangePredicate<ChangeData> {
Martin Fick5f6222912015-11-12 14:52:50 -0700677 ...
678 }
679
680 @Override
681 public Predicate<ChangeData> create(ChangeQueryBuilder builder, String value)
682 throws QueryParseException {
683 return new MyPredicate(value);
684 }
685}
686----
687
Craig Chapeldba4e892016-11-14 09:25:17 -0700688[[search_operands]]
689=== Search Operands ===
690
691Plugins can define new search operands to extend change searching.
692Plugin methods implementing search operands (returning a
693`Predicate<ChangeData>`), must be defined on a class implementing
694one of the `ChangeQueryBuilder.ChangeOperandsFactory` interfaces
695(.e.g., ChangeQueryBuilder.ChangeHasOperandFactory). The specific
696`ChangeOperandFactory` class must also be bound to the `DynamicSet` from
697a module's `configure()` method in the plugin.
698
699The new operand, when used in a search would appear as:
700 operatorName:operandName_pluginName
701
702A sample `ChangeHasOperandFactory` class implementing, and registering, a
703new `has:sample_pluginName` operand is shown below:
704
705====
706 @Singleton
707 public class SampleHasOperand implements ChangeHasOperandFactory {
708 public static class Module extends AbstractModule {
709 @Override
710 protected void configure() {
711 bind(ChangeHasOperandFactory.class)
712 .annotatedWith(Exports.named("sample")
713 .to(SampleHasOperand.class);
714 }
715 }
716
717 @Override
718 public Predicate<ChangeData> create(ChangeQueryBuilder builder)
719 throws QueryParseException {
720 return new HasSamplePredicate();
721 }
722====
723
Zac Livingston4f083a82016-05-20 12:38:43 -0600724[[command_options]]
725=== Command Options ===
726
727Plugins can provide additional options for each of the gerrit ssh and the
728REST API commands by implementing the DynamicBean interface and registering
729it to a command class name in the plugin module's `configure()` method. The
730plugin's name will be prepended to the name of each @Option annotation found
731on the DynamicBean object provided by the plugin. The example below shows a
732plugin that adds an option to log a value from the gerrit 'ban-commits'
733ssh command.
734
735[source, java]
736----
737public class SshModule extends AbstractModule {
738 private static final Logger log = LoggerFactory.getLogger(SshModule.class);
739
740 @Override
741 protected void configure() {
742 bind(DynamicOptions.DynamicBean.class)
743 .annotatedWith(Exports.named(
744 com.google.gerrit.sshd.commands.BanCommitCommand.class))
745 .to(BanOptions.class);
746 }
747
748 public static class BanOptions implements DynamicOptions.DynamicBean {
749 @Option(name = "--log", aliases = { "-l" }, usage = "Say Hello in the Log")
750 private void parse(String arg) {
751 log.error("Say Hello in the Log " + arg);
752 }
753 }
754----
Craig Chapeldba4e892016-11-14 09:25:17 -0700755
Zac Livingstoncffb24592016-11-13 09:08:08 -0700756[[query_attributes]]
757=== Query Attributes ===
758
759Plugins can provide additional attributes to be returned in Gerrit queries by
760implementing the ChangeAttributeFactory interface and registering it to the
761ChangeQueryProcessor.ChangeAttributeFactory class in the plugin module's
762'configure()' method. The new attribute(s) will be output under a "plugin"
763attribute in the change query output.
764
765The example below shows a plugin that adds two attributes ('exampleName' and
766'changeValue'), to the change query output.
767
768[source, java]
769----
770public class Module extends AbstractModule {
771 @Override
772 protected void configure() {
773 bind(ChangeAttributeFactory.class)
774 .annotatedWith(Exports.named("example"))
775 .to(AttributeFactory.class);
776 }
777}
778
779public class AttributeFactory implements ChangeAttributeFactory {
780
781 public class PluginAttribute extends PluginDefinedInfo {
782 public String exampleName;
783 public String changeValue;
784
785 public PluginAttribute(ChangeData c) {
786 this.exampleName = "Attribute Example";
787 this.changeValue = Integer.toString(c.getId().get());
788 }
789 }
790
791 @Override
792 public PluginDefinedInfo create(ChangeData c, ChangeQueryProcessor qp, String plugin) {
793 return new PluginAttribute(c);
794 }
795}
796----
797
798Example
799----
800
801ssh -p 29418 localhost gerrit query "change:1" --format json
802
803Output:
804
805{
806 "url" : "http://localhost:8080/1",
807 "plugins" : [
808 {
809 "name" : "myplugin-name",
810 "exampleName" : "Attribute Example",
811 "changeValue" : "1"
812 }
813 ],
814 ...
815}
816----
817
Edwin Kempin78ca0942013-10-30 11:24:06 +0100818[[simple-configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800819== Simple Configuration in `gerrit.config`
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200820
821In Gerrit, global configuration is stored in the `gerrit.config` file.
822If a plugin needs global configuration, this configuration should be
823stored in a `plugin` subsection in the `gerrit.config` file.
824
Edwin Kempinc9b68602013-10-30 09:32:43 +0100825This approach of storing the plugin configuration is only suitable for
826plugins that have a simple configuration that only consists of
827key-value pairs. With this approach it is not possible to have
828subsections in the plugin configuration. Plugins that require a complex
Edwin Kempin78ca0942013-10-30 11:24:06 +0100829configuration need to store their configuration in their
830link:#configuration[own configuration file] where they can make use of
831subsections. On the other hand storing the plugin configuration in a
832'plugin' subsection in the `gerrit.config` file has the advantage that
833administrators have all configuration parameters in one file, instead
834of having one configuration file per plugin.
Edwin Kempinc9b68602013-10-30 09:32:43 +0100835
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200836To avoid conflicts with other plugins, it is recommended that plugins
837only use the `plugin` subsection with their own name. For example the
838`helloworld` plugin should store its configuration in the
839`plugin.helloworld` subsection:
840
841----
842[plugin "helloworld"]
843 language = Latin
844----
845
Sasa Zivkovacdf5332013-09-20 14:05:15 +0200846Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200847plugin can easily access its configuration and there is no need for a
848plugin to parse the `gerrit.config` file on its own:
849
850[source,java]
851----
David Pursehouse529ec252013-09-27 13:45:14 +0900852@Inject
853private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200854
David Pursehoused128c892013-10-22 21:52:21 +0900855[...]
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200856
Edwin Kempin122622d2013-10-29 16:45:44 +0100857String language = cfg.getFromGerritConfig("helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +0900858 .getString("language", "English");
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200859----
860
Edwin Kempin78ca0942013-10-30 11:24:06 +0100861[[configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800862== Configuration in own config file
Edwin Kempin78ca0942013-10-30 11:24:06 +0100863
864Plugins can store their configuration in an own configuration file.
865This makes sense if the plugin configuration is rather complex and
866requires the usage of subsections. Plugins that have a simple
867key-value pair configuration can store their configuration in a
868link:#simple-configuration[`plugin` subsection of the `gerrit.config`
869file].
870
871The plugin configuration file must be named after the plugin and must
872be located in the `etc` folder of the review site. For example a
873configuration file for a `default-reviewer` plugin could look like
874this:
875
876.$site_path/etc/default-reviewer.config
877----
878[branch "refs/heads/master"]
879 reviewer = Project Owners
880 reviewer = john.doe@example.com
881[match "file:^.*\.txt"]
882 reviewer = My Info Developers
883----
884
David Pursehouse5b47bc42016-07-22 11:00:25 +0900885Plugins that have sensitive configuration settings can store those settings in
886an own secure configuration file. The plugin's secure configuration file must be
887named after the plugin and must be located in the `etc` folder of the review
888site. For example a secure configuration file for a `default-reviewer` plugin
889could look like this:
890
891.$site_path/etc/default-reviewer.secure.config
892----
893[auth]
894 password = secret
895----
896
Edwin Kempin78ca0942013-10-30 11:24:06 +0100897Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
898plugin can easily access its configuration:
899
900[source,java]
901----
902@Inject
903private com.google.gerrit.server.config.PluginConfigFactory cfg;
904
905[...]
906
907String[] reviewers = cfg.getGlobalPluginConfig("default-reviewer")
908 .getStringList("branch", "refs/heads/master", "reviewer");
David Pursehouse5b47bc42016-07-22 11:00:25 +0900909String password = cfg.getGlobalPluginConfig("default-reviewer")
910 .getString("auth", null, "password");
Edwin Kempin78ca0942013-10-30 11:24:06 +0100911----
912
Edwin Kempin78ca0942013-10-30 11:24:06 +0100913
Edwin Kempin705f2842013-10-30 14:25:31 +0100914[[simple-project-specific-configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800915== Simple Project Specific Configuration in `project.config`
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200916
917In Gerrit, project specific configuration is stored in the project's
918`project.config` file on the `refs/meta/config` branch. If a plugin
919needs configuration on project level (e.g. to enable its functionality
920only for certain projects), this configuration should be stored in a
921`plugin` subsection in the project's `project.config` file.
922
Edwin Kempinc9b68602013-10-30 09:32:43 +0100923This approach of storing the plugin configuration is only suitable for
924plugins that have a simple configuration that only consists of
925key-value pairs. With this approach it is not possible to have
926subsections in the plugin configuration. Plugins that require a complex
Edwin Kempin705f2842013-10-30 14:25:31 +0100927configuration need to store their configuration in their
928link:#project-specific-configuration[own configuration file] where they
929can make use of subsections. On the other hand storing the plugin
930configuration in a 'plugin' subsection in the `project.config` file has
931the advantage that project owners have all configuration parameters in
932one file, instead of having one configuration file per plugin.
Edwin Kempinc9b68602013-10-30 09:32:43 +0100933
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200934To avoid conflicts with other plugins, it is recommended that plugins
935only use the `plugin` subsection with their own name. For example the
936`helloworld` plugin should store its configuration in the
937`plugin.helloworld` subsection:
938
939----
940 [plugin "helloworld"]
941 enabled = true
942----
943
944Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
945plugin can easily access its project specific configuration and there
946is no need for a plugin to parse the `project.config` file on its own:
947
948[source,java]
949----
David Pursehouse529ec252013-09-27 13:45:14 +0900950@Inject
951private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200952
David Pursehoused128c892013-10-22 21:52:21 +0900953[...]
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200954
Edwin Kempin122622d2013-10-29 16:45:44 +0100955boolean enabled = cfg.getFromProjectConfig(project, "helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +0900956 .getBoolean("enabled", false);
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200957----
958
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200959It is also possible to get missing configuration parameters inherited
960from the parent projects:
961
962[source,java]
963----
David Pursehouse529ec252013-09-27 13:45:14 +0900964@Inject
965private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200966
David Pursehoused128c892013-10-22 21:52:21 +0900967[...]
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200968
Edwin Kempin122622d2013-10-29 16:45:44 +0100969boolean enabled = cfg.getFromProjectConfigWithInheritance(project, "helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +0900970 .getBoolean("enabled", false);
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200971----
972
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200973Project owners can edit the project configuration by fetching the
974`refs/meta/config` branch, editing the `project.config` file and
975pushing the commit back.
976
Edwin Kempin9ce4f552013-11-15 16:00:00 +0100977Plugin configuration values that are stored in the `project.config`
978file can be exposed in the ProjectInfoScreen to allow project owners
979to see and edit them from the UI.
980
981For this an instance of `ProjectConfigEntry` needs to be bound for each
982parameter. The export name must be a valid Git variable name. The
983variable name is case-insensitive, allows only alphanumeric characters
984and '-', and must start with an alphabetic character.
985
Edwin Kempina6c1c452013-11-28 16:55:22 +0100986The example below shows how the parameters `plugin.helloworld.enabled`
987and `plugin.helloworld.language` are bound to be editable from the
David Pursehousea1d633b2014-05-02 17:21:02 +0900988Web UI. For the parameter `plugin.helloworld.enabled` "Enable Greeting"
Edwin Kempina6c1c452013-11-28 16:55:22 +0100989is provided as display name and the default value is set to `true`.
990For the parameter `plugin.helloworld.language` "Preferred Language"
991is provided as display name and "en" is set as default value.
Edwin Kempin9ce4f552013-11-15 16:00:00 +0100992
993[source,java]
994----
995class Module extends AbstractModule {
996 @Override
997 protected void configure() {
998 bind(ProjectConfigEntry.class)
Edwin Kempina6c1c452013-11-28 16:55:22 +0100999 .annotatedWith(Exports.named("enabled"))
1000 .toInstance(new ProjectConfigEntry("Enable Greeting", true));
1001 bind(ProjectConfigEntry.class)
Edwin Kempin9ce4f552013-11-15 16:00:00 +01001002 .annotatedWith(Exports.named("language"))
1003 .toInstance(new ProjectConfigEntry("Preferred Language", "en"));
1004 }
1005}
1006----
1007
Edwin Kempinb64d3972013-11-17 18:55:48 +01001008By overwriting the `onUpdate` method of `ProjectConfigEntry` plugins
1009can be notified when this configuration parameter is updated on a
1010project.
1011
Janice Agustine5a9d012015-08-24 09:05:56 -04001012[[configuring-groups]]
1013=== Referencing groups in `project.config`
1014
1015Plugins can refer to groups so that when they are renamed, the project
1016config will also be updated in this section. The proper format to use is
Hugo Arès532e0a32017-06-16 09:31:08 -04001017the same as for any other group reference in the `project.config`, as shown below.
Janice Agustine5a9d012015-08-24 09:05:56 -04001018
1019----
Hugo Arès532e0a32017-06-16 09:31:08 -04001020group group_name
Janice Agustine5a9d012015-08-24 09:05:56 -04001021----
1022
Hugo Arès532e0a32017-06-16 09:31:08 -04001023The file `groups` must also contains the mapping of the group name and its UUID,
1024refer to link:config-project-config.html#file-groups[file groups]
1025
Edwin Kempin705f2842013-10-30 14:25:31 +01001026[[project-specific-configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001027== Project Specific Configuration in own config file
Edwin Kempin705f2842013-10-30 14:25:31 +01001028
1029Plugins can store their project specific configuration in an own
1030configuration file in the projects `refs/meta/config` branch.
1031This makes sense if the plugins project specific configuration is
1032rather complex and requires the usage of subsections. Plugins that
1033have a simple key-value pair configuration can store their project
1034specific configuration in a link:#simple-project-specific-configuration[
1035`plugin` subsection of the `project.config` file].
1036
1037The plugin configuration file in the `refs/meta/config` branch must be
1038named after the plugin. For example a configuration file for a
1039`default-reviewer` plugin could look like this:
1040
1041.default-reviewer.config
1042----
1043[branch "refs/heads/master"]
1044 reviewer = Project Owners
1045 reviewer = john.doe@example.com
1046[match "file:^.*\.txt"]
1047 reviewer = My Info Developers
1048----
1049
1050Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
1051plugin can easily access its project specific configuration:
1052
1053[source,java]
1054----
1055@Inject
1056private com.google.gerrit.server.config.PluginConfigFactory cfg;
1057
1058[...]
1059
1060String[] reviewers = cfg.getProjectPluginConfig(project, "default-reviewer")
1061 .getStringList("branch", "refs/heads/master", "reviewer");
1062----
1063
Edwin Kempin762da382013-10-30 14:50:01 +01001064It is also possible to get missing configuration parameters inherited
1065from the parent projects:
1066
1067[source,java]
1068----
1069@Inject
1070private com.google.gerrit.server.config.PluginConfigFactory cfg;
1071
1072[...]
1073
David Ostrovsky468e4c32014-03-22 06:05:35 -07001074String[] reviewers = cfg.getProjectPluginConfigWithInheritance(project, "default-reviewer")
Edwin Kempin762da382013-10-30 14:50:01 +01001075 .getStringList("branch", "refs/heads/master", "reviewer");
1076----
1077
Edwin Kempin705f2842013-10-30 14:25:31 +01001078Project owners can edit the project configuration by fetching the
1079`refs/meta/config` branch, editing the `<plugin-name>.config` file and
1080pushing the commit back.
1081
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001082== React on changes in project configuration
Edwin Kempina46b6c92013-12-04 21:05:24 +01001083
1084If a plugin wants to react on changes in the project configuration, it
1085can implement a `GitReferenceUpdatedListener` and filter on events for
1086the `refs/meta/config` branch:
1087
1088[source,java]
1089----
1090public class MyListener implements GitReferenceUpdatedListener {
1091
1092 private final MetaDataUpdate.Server metaDataUpdateFactory;
1093
1094 @Inject
1095 MyListener(MetaDataUpdate.Server metaDataUpdateFactory) {
1096 this.metaDataUpdateFactory = metaDataUpdateFactory;
1097 }
1098
1099 @Override
1100 public void onGitReferenceUpdated(Event event) {
Edwin Kempina951ba52014-01-03 14:07:28 +01001101 if (event.getRefName().equals(RefNames.REFS_CONFIG)) {
Edwin Kempina46b6c92013-12-04 21:05:24 +01001102 Project.NameKey p = new Project.NameKey(event.getProjectName());
1103 try {
Edwin Kempina951ba52014-01-03 14:07:28 +01001104 ProjectConfig oldCfg = parseConfig(p, event.getOldObjectId());
1105 ProjectConfig newCfg = parseConfig(p, event.getNewObjectId());
Edwin Kempina46b6c92013-12-04 21:05:24 +01001106
Edwin Kempina951ba52014-01-03 14:07:28 +01001107 if (oldCfg != null && newCfg != null
1108 && !oldCfg.getProject().getSubmitType().equals(newCfg.getProject().getSubmitType())) {
Edwin Kempina46b6c92013-12-04 21:05:24 +01001109 // submit type has changed
1110 ...
1111 }
1112 } catch (IOException | ConfigInvalidException e) {
1113 ...
1114 }
1115 }
1116 }
Edwin Kempina951ba52014-01-03 14:07:28 +01001117
1118 private ProjectConfig parseConfig(Project.NameKey p, String idStr)
1119 throws IOException, ConfigInvalidException, RepositoryNotFoundException {
1120 ObjectId id = ObjectId.fromString(idStr);
1121 if (ObjectId.zeroId().equals(id)) {
1122 return null;
1123 }
1124 return ProjectConfig.read(metaDataUpdateFactory.create(p), id);
1125 }
Edwin Kempina46b6c92013-12-04 21:05:24 +01001126}
1127----
1128
1129
David Ostrovsky7066cc02013-06-15 14:46:23 +02001130[[capabilities]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001131== Plugin Owned Capabilities
David Ostrovsky7066cc02013-06-15 14:46:23 +02001132
1133Plugins may provide their own capabilities and restrict usage of SSH
Dariusz Luksza112d93a2014-06-01 16:52:23 +02001134commands or `UiAction` to the users who are granted those capabilities.
David Ostrovsky7066cc02013-06-15 14:46:23 +02001135
1136Plugins define the capabilities by overriding the `CapabilityDefinition`
1137abstract class:
1138
David Pursehouse68153d72013-09-04 10:09:17 +09001139[source,java]
1140----
1141public class PrintHelloCapability extends CapabilityDefinition {
1142 @Override
1143 public String getDescription() {
1144 return "Print Hello";
David Ostrovsky7066cc02013-06-15 14:46:23 +02001145 }
David Pursehouse68153d72013-09-04 10:09:17 +09001146}
1147----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001148
Dariusz Luksza112d93a2014-06-01 16:52:23 +02001149If no Guice modules are declared in the manifest, capability may
David Ostrovsky7066cc02013-06-15 14:46:23 +02001150use auto-registration by providing an `@Export` annotation:
1151
David Pursehouse68153d72013-09-04 10:09:17 +09001152[source,java]
1153----
1154@Export("printHello")
1155public class PrintHelloCapability extends CapabilityDefinition {
David Pursehoused128c892013-10-22 21:52:21 +09001156 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001157}
1158----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001159
1160Otherwise the capability must be bound in a plugin module:
1161
David Pursehouse68153d72013-09-04 10:09:17 +09001162[source,java]
1163----
1164public class HelloWorldModule extends AbstractModule {
1165 @Override
1166 protected void configure() {
1167 bind(CapabilityDefinition.class)
1168 .annotatedWith(Exports.named("printHello"))
1169 .to(PrintHelloCapability.class);
David Ostrovsky7066cc02013-06-15 14:46:23 +02001170 }
David Pursehouse68153d72013-09-04 10:09:17 +09001171}
1172----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001173
1174With a plugin-owned capability defined in this way, it is possible to restrict
David Ostrovskyf86bae52013-09-01 09:10:39 +02001175usage of an SSH command or `UiAction` to members of the group that were granted
David Ostrovsky7066cc02013-06-15 14:46:23 +02001176this capability in the usual way, using the `RequiresCapability` annotation:
1177
David Pursehouse68153d72013-09-04 10:09:17 +09001178[source,java]
1179----
1180@RequiresCapability("printHello")
1181@CommandMetaData(name="print", description="Print greeting in different languages")
1182public final class PrintHelloWorldCommand extends SshCommand {
David Pursehoused128c892013-10-22 21:52:21 +09001183 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001184}
1185----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001186
David Ostrovskyf86bae52013-09-01 09:10:39 +02001187Or with `UiAction`:
David Ostrovsky7066cc02013-06-15 14:46:23 +02001188
David Pursehouse68153d72013-09-04 10:09:17 +09001189[source,java]
1190----
1191@RequiresCapability("printHello")
1192public class SayHelloAction extends UiAction<RevisionResource>
1193 implements RestModifyView<RevisionResource, SayHelloAction.Input> {
David Pursehoused128c892013-10-22 21:52:21 +09001194 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001195}
1196----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001197
1198Capability scope was introduced to differentiate between plugin-owned
David Pursehousebf053342013-09-05 14:55:29 +09001199capabilities and core capabilities. Per default the scope of the
1200`@RequiresCapability` annotation is `CapabilityScope.CONTEXT`, that means:
1201
David Ostrovsky7066cc02013-06-15 14:46:23 +02001202* when `@RequiresCapability` is used within a plugin the scope of the
1203capability is assumed to be that plugin.
David Pursehousebf053342013-09-05 14:55:29 +09001204
David Ostrovsky7066cc02013-06-15 14:46:23 +02001205* If `@RequiresCapability` is used within the core Gerrit Code Review server
1206(and thus is outside of a plugin) the scope is the core server and will use
1207the `GlobalCapability` known to Gerrit Code Review server.
1208
1209If a plugin needs to use a core capability name (e.g. "administrateServer")
1210this can be specified by setting `scope = CapabilityScope.CORE`:
1211
David Pursehouse68153d72013-09-04 10:09:17 +09001212[source,java]
1213----
1214@RequiresCapability(value = "administrateServer", scope =
1215 CapabilityScope.CORE)
David Pursehoused128c892013-10-22 21:52:21 +09001216 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001217----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001218
David Ostrovskyf86bae52013-09-01 09:10:39 +02001219[[ui_extension]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001220== UI Extension
David Ostrovskyf86bae52013-09-01 09:10:39 +02001221
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001222[[panels]]
1223=== Panels
1224
1225GWT plugins can contribute panels to Gerrit screens.
1226
1227Gerrit screens define extension points where plugins can add GWT
1228panels with custom controls:
1229
1230* Change Screen:
Edwin Kempin2a8c5152015-07-08 14:28:57 +02001231** `GerritUiExtensionPoint.CHANGE_SCREEN_HEADER`:
1232+
1233Panel will be shown in the header bar to the right of the change
1234status.
1235
Edwin Kempin745021e2015-07-09 13:09:44 +02001236** `GerritUiExtensionPoint.CHANGE_SCREEN_HEADER_RIGHT_OF_BUTTONS`:
1237+
1238Panel will be shown in the header bar on the right side of the buttons.
1239
Edwin Kempincbc95252015-07-09 11:37:53 +02001240** `GerritUiExtensionPoint.CHANGE_SCREEN_HEADER_RIGHT_OF_POP_DOWNS`:
1241+
1242Panel will be shown in the header bar on the right side of the pop down
1243buttons.
1244
Khai Do675afc02016-07-28 16:30:37 -07001245** `GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_COMMIT_INFO_BLOCK`:
1246+
1247Panel will be shown below the commit info block.
1248
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001249** `GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK`:
1250+
1251Panel will be shown below the change info block.
1252
Khai Do76c830c2016-07-28 16:35:45 -07001253** `GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_RELATED_INFO_BLOCK`:
1254+
1255Panel will be shown below the related info block.
1256
Khai Do83940ba2016-09-20 15:15:45 +02001257** `GerritUiExtensionPoint.CHANGE_SCREEN_HISTORY_RIGHT_OF_BUTTONS`:
1258+
1259Panel will be shown in the history bar on the right side of the buttons.
1260
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001261** The following parameters are provided:
Edwin Kempin5d683cc2015-07-10 15:47:14 +02001262*** `GerritUiExtensionPoint.Key.CHANGE_INFO`:
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001263+
Edwin Kempin5d683cc2015-07-10 15:47:14 +02001264The link:rest-api-changes.html#change-info[ChangeInfo] entity for the
1265current change.
David Ostrovsky916ae0c2016-03-15 17:05:41 +01001266+
1267The link:rest-api-changes.html#revision-info[RevisionInfo] entity for
1268the current patch set.
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001269
Edwin Kempin88b947a2015-07-08 09:03:56 +02001270* Project Info Screen:
1271** `GerritUiExtensionPoint.PROJECT_INFO_SCREEN_TOP`:
1272+
1273Panel will be shown at the top of the screen.
1274
1275** `GerritUiExtensionPoint.PROJECT_INFO_SCREEN_BOTTOM`:
1276+
1277Panel will be shown at the bottom of the screen.
1278
1279** The following parameters are provided:
1280*** `GerritUiExtensionPoint.Key.PROJECT_NAME`:
1281+
1282The name of the project.
1283
Edwin Kempin241d9db2015-07-08 13:53:50 +02001284* User Password Screen:
1285** `GerritUiExtensionPoint.PASSWORD_SCREEN_BOTTOM`:
1286+
1287Panel will be shown at the bottom of the screen.
1288
Edwin Kempin30c6f472015-07-09 14:27:52 +02001289** The following parameters are provided:
1290*** `GerritUiExtensionPoint.Key.ACCOUNT_INFO`:
1291+
1292The link:rest-api-accounts.html#account-info[AccountInfo] entity for
1293the current user.
1294
Edwin Kempin1cd95f92015-07-14 08:27:20 +02001295* User Preferences Screen:
1296** `GerritUiExtensionPoint.PREFERENCES_SCREEN_BOTTOM`:
1297+
1298Panel will be shown at the bottom of the screen.
1299
1300** The following parameters are provided:
1301*** `GerritUiExtensionPoint.Key.ACCOUNT_INFO`:
1302+
1303The link:rest-api-accounts.html#account-info[AccountInfo] entity for
1304the current user.
1305
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001306* User Profile Screen:
1307** `GerritUiExtensionPoint.PROFILE_SCREEN_BOTTOM`:
1308+
1309Panel will be shown at the bottom of the screen below the grid with the
1310profile data.
1311
Edwin Kempin30c6f472015-07-09 14:27:52 +02001312** The following parameters are provided:
1313*** `GerritUiExtensionPoint.Key.ACCOUNT_INFO`:
1314+
1315The link:rest-api-accounts.html#account-info[AccountInfo] entity for
1316the current user.
1317
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001318Example panel:
1319[source,java]
1320----
1321public class MyPlugin extends PluginEntryPoint {
1322 @Override
1323 public void onPluginLoad() {
1324 Plugin.get().panel(GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK,
Zac Livingstone7f3d1a2017-03-01 12:47:37 -07001325 "my_panel_name",
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001326 new Panel.EntryPoint() {
1327 @Override
1328 public void onLoad(Panel panel) {
1329 panel.setWidget(new InlineLabel("My Panel for change "
1330 + panel.getInt(GerritUiExtensionPoint.Key.CHANGE_ID, -1));
1331 }
1332 });
1333 }
1334}
1335----
1336
Zac Livingstone7f3d1a2017-03-01 12:47:37 -07001337Change Screen panel ordering may be specified in the
1338project config. Values may be either "plugin name" or
1339"plugin name"."panel name".
1340Panels not specified in the config will be added
1341to the end in load order. Panels specified in the config that
1342are not found will be ignored.
1343
1344Example config:
1345----
1346[extension-panels "CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK"]
1347 panel = helloworld.change_id
1348 panel = myotherplugin
1349 panel = myplugin.my_panel_name
1350----
1351
1352
1353
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001354[[actions]]
1355=== Actions
1356
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001357Plugins can contribute UI actions on core Gerrit pages. This is useful
1358for workflow customization or exposing plugin functionality through the
1359UI in addition to SSH commands and the REST API.
David Ostrovskyf86bae52013-09-01 09:10:39 +02001360
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001361For instance a plugin to integrate Jira with Gerrit changes may
1362contribute a "File bug" button to allow filing a bug from the change
1363page or plugins to integrate continuous integration systems may
1364contribute a "Schedule" button to allow a CI build to be scheduled
1365manually from the patch set panel.
David Ostrovskyf86bae52013-09-01 09:10:39 +02001366
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001367Two different places on core Gerrit pages are supported:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001368
1369* Change screen
1370* Project info screen
1371
1372Plugins contribute UI actions by implementing the `UiAction` interface:
1373
David Pursehouse68153d72013-09-04 10:09:17 +09001374[source,java]
1375----
1376@RequiresCapability("printHello")
1377class HelloWorldAction implements UiAction<RevisionResource>,
1378 RestModifyView<RevisionResource, HelloWorldAction.Input> {
1379 static class Input {
1380 boolean french;
1381 String message;
David Ostrovskyf86bae52013-09-01 09:10:39 +02001382 }
David Pursehouse68153d72013-09-04 10:09:17 +09001383
1384 private Provider<CurrentUser> user;
1385
1386 @Inject
1387 HelloWorldAction(Provider<CurrentUser> user) {
1388 this.user = user;
1389 }
1390
1391 @Override
1392 public String apply(RevisionResource rev, Input input) {
1393 final String greeting = input.french
1394 ? "Bonjour"
1395 : "Hello";
1396 return String.format("%s %s from change %s, patch set %d!",
1397 greeting,
1398 Strings.isNullOrEmpty(input.message)
1399 ? Objects.firstNonNull(user.get().getUserName(), "world")
1400 : input.message,
1401 rev.getChange().getId().toString(),
1402 rev.getPatchSet().getPatchSetId());
1403 }
1404
1405 @Override
1406 public Description getDescription(
1407 RevisionResource resource) {
1408 return new Description()
1409 .setLabel("Say hello")
1410 .setTitle("Say hello in different languages");
1411 }
1412}
1413----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001414
David Ostrovsky450eefe2013-10-21 21:18:11 +02001415Sometimes plugins may want to be able to change the state of a patch set or
1416change in the `UiAction.apply()` method and reflect these changes on the core
1417UI. For example a buildbot plugin which exposes a 'Schedule' button on the
1418patch set panel may want to disable that button after the build was scheduled
1419and update the tooltip of that button. But because of Gerrit's caching
1420strategy the following must be taken into consideration.
1421
1422The browser is allowed to cache the `UiAction` information until something on
1423the change is modified. More accurately the change row needs to be modified in
1424the database to have a more recent `lastUpdatedOn` or a new `rowVersion`, or
1425the +refs/meta/config+ of the project or any parents needs to change to a new
1426SHA-1. The ETag SHA-1 computation code can be found in the
1427`ChangeResource.getETag()` method.
1428
David Pursehoused128c892013-10-22 21:52:21 +09001429The easiest way to accomplish this is to update `lastUpdatedOn` of the change:
David Ostrovsky450eefe2013-10-21 21:18:11 +02001430
1431[source,java]
1432----
1433@Override
1434public Object apply(RevisionResource rcrs, Input in) {
1435 // schedule a build
1436 [...]
1437 // update change
1438 ReviewDb db = dbProvider.get();
Edwin Kempine2d06b02016-02-17 18:34:17 +01001439 try (BatchUpdate bu = batchUpdateFactory.create(
1440 db, project.getNameKey(), user, TimeUtil.nowTs())) {
1441 bu.addOp(change.getId(), new BatchUpdate.Op() {
1442 @Override
Dave Borowitzb91cf222017-03-10 13:11:59 -05001443 public boolean updateChange(ChangeContext ctx) {
Edwin Kempine2d06b02016-02-17 18:34:17 +01001444 return true;
1445 }
1446 });
1447 bu.execute();
David Ostrovsky450eefe2013-10-21 21:18:11 +02001448 }
David Pursehoused128c892013-10-22 21:52:21 +09001449 [...]
David Ostrovsky450eefe2013-10-21 21:18:11 +02001450}
1451----
1452
David Ostrovskyf86bae52013-09-01 09:10:39 +02001453`UiAction` must be bound in a plugin module:
1454
David Pursehouse68153d72013-09-04 10:09:17 +09001455[source,java]
1456----
1457public class Module extends AbstractModule {
1458 @Override
1459 protected void configure() {
1460 install(new RestApiModule() {
1461 @Override
1462 protected void configure() {
1463 post(REVISION_KIND, "say-hello")
1464 .to(HelloWorldAction.class);
1465 }
1466 });
David Ostrovskyf86bae52013-09-01 09:10:39 +02001467 }
David Pursehouse68153d72013-09-04 10:09:17 +09001468}
1469----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001470
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001471The module above must be declared in the `pom.xml` for Maven driven
1472plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001473
David Pursehouse68153d72013-09-04 10:09:17 +09001474[source,xml]
1475----
1476<manifestEntries>
1477 <Gerrit-Module>com.googlesource.gerrit.plugins.cookbook.Module</Gerrit-Module>
1478</manifestEntries>
1479----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001480
David Ostrovskyfdbfcad2016-11-15 06:35:29 -08001481or in the `BUILD` configuration file for Bazel driven plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001482
David Pursehouse68153d72013-09-04 10:09:17 +09001483[source,python]
1484----
1485manifest_entries = [
1486 'Gerrit-Module: com.googlesource.gerrit.plugins.cookbook.Module',
1487]
1488----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001489
1490In some use cases more user input must be gathered, for that `UiAction` can be
1491combined with the JavaScript API. This would display a small popup near the
1492activation button to gather additional input from the user. The JS file is
1493typically put in the `static` folder within the plugin's directory:
1494
David Pursehouse68153d72013-09-04 10:09:17 +09001495[source,javascript]
1496----
1497Gerrit.install(function(self) {
1498 function onSayHello(c) {
1499 var f = c.textfield();
1500 var t = c.checkbox();
1501 var b = c.button('Say hello', {onclick: function(){
1502 c.call(
1503 {message: f.value, french: t.checked},
1504 function(r) {
1505 c.hide();
1506 window.alert(r);
1507 c.refresh();
1508 });
1509 }});
1510 c.popup(c.div(
1511 c.prependLabel('Greeting message', f),
1512 c.br(),
1513 c.label(t, 'french'),
1514 c.br(),
1515 b));
1516 f.focus();
1517 }
1518 self.onAction('revision', 'say-hello', onSayHello);
1519});
1520----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001521
1522The JS module must be exposed as a `WebUiPlugin` and bound as
1523an HTTP Module:
1524
David Pursehouse68153d72013-09-04 10:09:17 +09001525[source,java]
1526----
1527public class HttpModule extends HttpPluginModule {
1528 @Override
1529 protected void configureServlets() {
1530 DynamicSet.bind(binder(), WebUiPlugin.class)
1531 .toInstance(new JavaScriptPlugin("hello.js"));
David Ostrovskyf86bae52013-09-01 09:10:39 +02001532 }
David Pursehouse68153d72013-09-04 10:09:17 +09001533}
1534----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001535
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001536The HTTP module above must be declared in the `pom.xml` for Maven
1537driven plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001538
David Pursehouse68153d72013-09-04 10:09:17 +09001539[source,xml]
1540----
1541<manifestEntries>
1542 <Gerrit-HttpModule>com.googlesource.gerrit.plugins.cookbook.HttpModule</Gerrit-HttpModule>
1543</manifestEntries>
1544----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001545
David Ostrovskyfdbfcad2016-11-15 06:35:29 -08001546or in the `BUILD` configuration file for Bazel driven plugins
David Ostrovskyf86bae52013-09-01 09:10:39 +02001547
David Pursehouse68153d72013-09-04 10:09:17 +09001548[source,python]
1549----
1550manifest_entries = [
1551 'Gerrit-HttpModule: com.googlesource.gerrit.plugins.cookbook.HttpModule',
1552]
1553----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001554
1555If `UiAction` is annotated with the `@RequiresCapability` annotation, then the
1556capability check is done during the `UiAction` gathering, so the plugin author
1557doesn't have to set `UiAction.Description.setVisible()` explicitly in this
1558case.
1559
David Pursehousea61ee502016-09-06 16:27:09 +09001560The following prerequisites must be met, to satisfy the capability check:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001561
1562* user is authenticated
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001563* user is a member of a group which has the `Administrate Server` capability, or
David Ostrovskyf86bae52013-09-01 09:10:39 +02001564* user is a member of a group which has the required capability
1565
1566The `apply` method is called when the button is clicked. If `UiAction` is
1567combined with JavaScript API (its own JavaScript function is provided),
1568then a popup dialog is normally opened to gather additional user input.
1569A new button is placed on the popup dialog to actually send the request.
1570
1571Every `UiAction` exposes a REST API endpoint. The endpoint from the example above
1572can be accessed from any REST client, i. e.:
1573
Michael Ochmannb99feab2016-07-06 14:10:22 +02001574----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001575 curl -X POST -H "Content-Type: application/json" \
1576 -d '{message: "François", french: true}' \
Han-Wen Nienhuys84d830b2017-02-15 16:36:04 +01001577 --user joe:secret \
David Ostrovskyf86bae52013-09-01 09:10:39 +02001578 http://host:port/a/changes/1/revisions/1/cookbook~say-hello
1579 "Bonjour François from change 1, patch set 1!"
Michael Ochmannb99feab2016-07-06 14:10:22 +02001580----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001581
David Pursehouse42245822013-09-24 09:48:20 +09001582A special case is to bind an endpoint without a view name. This is
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001583particularly useful for `DELETE` requests:
David Ostrovskyc6d19ed2013-09-20 21:30:18 +02001584
1585[source,java]
1586----
1587public class Module extends AbstractModule {
1588 @Override
1589 protected void configure() {
1590 install(new RestApiModule() {
1591 @Override
1592 protected void configure() {
1593 delete(PROJECT_KIND)
1594 .to(DeleteProject.class);
1595 }
1596 });
1597 }
1598}
1599----
1600
David Pursehouse42245822013-09-24 09:48:20 +09001601For a `UiAction` bound this way, a JS API function can be provided.
1602
1603Currently only one restriction exists: per plugin only one `UiAction`
David Ostrovskyc6d19ed2013-09-20 21:30:18 +02001604can be bound per resource without view name. To define a JS function
1605for the `UiAction`, "/" must be used as the name:
1606
1607[source,javascript]
1608----
1609Gerrit.install(function(self) {
1610 function onDeleteProject(c) {
1611 [...]
1612 }
1613 self.onAction('project', '/', onDeleteProject);
1614});
1615----
1616
Dave Borowitzf1790ce2016-11-14 12:26:12 -08001617
1618[[action-visitor]]
1619=== Action Visitors
1620
1621In addition to providing new actions, plugins can have fine-grained control
1622over the link:rest-api-changes.html#action-info[ActionInfo] map, modifying or
1623removing existing actions, including those contributed by core.
1624
1625Visitors are provided the link:rest-api-changes.html#action-info[ActionInfo],
1626which is mutable, along with copies of the
1627link:rest-api-changes.html#change-info[ChangeInfo] and
1628link:rest-api-changes.html#revision-info[RevisionInfo]. They can modify the
1629action, or return `false` to exclude it from the resulting map.
1630
1631These operations only affect the action buttons that are displayed in the UI;
1632the underlying REST API endpoints are not affected. Multiple plugins may
1633implement the visitor interface, but the order in which they are run is
1634undefined.
1635
1636For example, to exclude "Cherry-Pick" only from certain projects, and rename
1637"Abandon":
1638
1639[source,java]
1640----
1641public class MyActionVisitor implements ActionVisitor {
1642 @Override
1643 public boolean visit(String name, ActionInfo actionInfo,
1644 ChangeInfo changeInfo) {
1645 if (name.equals("abandon")) {
1646 actionInfo.label = "Drop";
1647 }
1648 return true;
1649 }
1650
1651 @Override
1652 public boolean visit(String name, ActionInfo actionInfo,
1653 ChangeInfo changeInfo, RevisionInfo revisionInfo) {
1654 if (project.startsWith("some-team/") && name.equals("cherrypick")) {
1655 return false;
1656 }
1657 return true;
1658 }
1659}
1660----
1661
1662
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001663[[top-menu-extensions]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001664== Top Menu Extensions
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001665
1666Plugins can contribute items to Gerrit's top menu.
1667
1668A single top menu extension can have multiple elements and will be put as
1669the last element in Gerrit's top menu.
1670
1671Plugins define the top menu entries by implementing `TopMenu` interface:
1672
1673[source,java]
1674----
1675public class MyTopMenuExtension implements TopMenu {
1676
1677 @Override
1678 public List<MenuEntry> getEntries() {
1679 return Lists.newArrayList(
1680 new MenuEntry("Top Menu Entry", Lists.newArrayList(
1681 new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
1682 }
1683}
1684----
1685
Edwin Kempin77f23242013-09-30 14:53:20 +02001686Plugins can also add additional menu items to Gerrit's top menu entries
1687by defining a `MenuEntry` that has the same name as a Gerrit top menu
1688entry:
1689
1690[source,java]
1691----
1692public class MyTopMenuExtension implements TopMenu {
1693
1694 @Override
1695 public List<MenuEntry> getEntries() {
1696 return Lists.newArrayList(
Dariusz Luksza2d3afab2013-10-01 11:07:13 +02001697 new MenuEntry(GerritTopMenu.PROJECTS, Lists.newArrayList(
Edwin Kempin77f23242013-09-30 14:53:20 +02001698 new MenuItem("Browse Repositories", "https://gerrit.googlesource.com/"))));
1699 }
1700}
1701----
1702
Dariusz Lukszae8de74f2014-06-04 19:39:20 +02001703`MenuItems` that are bound for the `MenuEntry` with the name
1704`GerritTopMenu.PROJECTS` can contain a `${projectName}` placeholder
1705which is automatically replaced by the actual project name.
1706
1707E.g. plugins may register an link:#http[HTTP Servlet] to handle project
1708specific requests and add an menu item for this:
1709
1710[source,java]
1711---
1712 new MenuItem("My Screen", "/plugins/myplugin/project/${projectName}");
1713---
1714
1715This also enables plugins to provide menu items for project aware
1716screens:
1717
1718[source,java]
1719---
1720 new MenuItem("My Screen", "/x/my-screen/for/${projectName}");
1721---
1722
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001723If no Guice modules are declared in the manifest, the top menu extension may use
1724auto-registration by providing an `@Listen` annotation:
1725
1726[source,java]
1727----
1728@Listen
1729public class MyTopMenuExtension implements TopMenu {
David Pursehoused128c892013-10-22 21:52:21 +09001730 [...]
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001731}
1732----
1733
Luca Milanesiocb230402013-10-11 08:49:56 +01001734Otherwise the top menu extension must be bound in the plugin module used
1735for the Gerrit system injector (Gerrit-Module entry in MANIFEST.MF):
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001736
1737[source,java]
1738----
Luca Milanesiocb230402013-10-11 08:49:56 +01001739package com.googlesource.gerrit.plugins.helloworld;
1740
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001741public class HelloWorldModule extends AbstractModule {
1742 @Override
1743 protected void configure() {
1744 DynamicSet.bind(binder(), TopMenu.class).to(MyTopMenuExtension.class);
1745 }
1746}
1747----
1748
Luca Milanesiocb230402013-10-11 08:49:56 +01001749[source,manifest]
1750----
1751Gerrit-ApiType: plugin
1752Gerrit-Module: com.googlesource.gerrit.plugins.helloworld.HelloWorldModule
1753----
1754
Edwin Kempinb2e926a2013-11-11 16:38:30 +01001755It is also possible to show some menu entries only if the user has a
1756certain capability:
1757
1758[source,java]
1759----
1760public class MyTopMenuExtension implements TopMenu {
1761 private final String pluginName;
1762 private final Provider<CurrentUser> userProvider;
1763 private final List<MenuEntry> menuEntries;
1764
1765 @Inject
1766 public MyTopMenuExtension(@PluginName String pluginName,
1767 Provider<CurrentUser> userProvider) {
1768 this.pluginName = pluginName;
1769 this.userProvider = userProvider;
1770 menuEntries = new ArrayList<TopMenu.MenuEntry>();
1771
1772 // add menu entry that is only visible to users with a certain capability
1773 if (canSeeMenuEntry()) {
1774 menuEntries.add(new MenuEntry("Top Menu Entry", Collections
1775 .singletonList(new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
1776 }
1777
1778 // add menu entry that is visible to all users (even anonymous users)
1779 menuEntries.add(new MenuEntry("Top Menu Entry", Collections
1780 .singletonList(new MenuItem("Documentation", "/plugins/myplugin/"))));
1781 }
1782
1783 private boolean canSeeMenuEntry() {
1784 if (userProvider.get().isIdentifiedUser()) {
1785 CapabilityControl ctl = userProvider.get().getCapabilities();
1786 return ctl.canPerform(pluginName + "-" + MyCapability.ID)
1787 || ctl.canAdministrateServer();
1788 } else {
1789 return false;
1790 }
1791 }
1792
1793 @Override
1794 public List<MenuEntry> getEntries() {
1795 return menuEntries;
1796 }
1797}
1798----
1799
Dave Borowitzf1790ce2016-11-14 12:26:12 -08001800
Edwin Kempin3c024ea2013-11-11 10:43:46 +01001801[[gwt_ui_extension]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001802== GWT UI Extension
Edwin Kempin3c024ea2013-11-11 10:43:46 +01001803Plugins can extend the Gerrit UI with own GWT code.
1804
Edwin Kempinb74daa92013-11-11 11:28:16 +01001805A GWT plugin must contain a GWT module file, e.g. `HelloPlugin.gwt.xml`,
1806that bundles together all the configuration settings of the GWT plugin:
1807
1808[source,xml]
1809----
1810<?xml version="1.0" encoding="UTF-8"?>
1811<module rename-to="hello_gwt_plugin">
1812 <!-- Inherit the core Web Toolkit stuff. -->
1813 <inherits name="com.google.gwt.user.User"/>
1814 <!-- Other module inherits -->
1815 <inherits name="com.google.gerrit.Plugin"/>
1816 <inherits name="com.google.gwt.http.HTTP"/>
1817 <!-- Using GWT built-in themes adds a number of static -->
1818 <!-- resources to the plugin. No theme inherits lines were -->
1819 <!-- added in order to make this plugin as simple as possible -->
1820 <!-- Specify the app entry point class. -->
1821 <entry-point class="${package}.client.HelloPlugin"/>
1822 <stylesheet src="hello.css"/>
1823</module>
1824----
1825
1826The GWT module must inherit `com.google.gerrit.Plugin` and
1827`com.google.gwt.http.HTTP`.
1828
1829To register the GWT module a `GwtPlugin` needs to be bound.
1830
1831If no Guice modules are declared in the manifest, the GWT plugin may
1832use auto-registration by using the `@Listen` annotation:
1833
1834[source,java]
1835----
1836@Listen
1837public class MyExtension extends GwtPlugin {
1838 public MyExtension() {
1839 super("hello_gwt_plugin");
1840 }
1841}
1842----
1843
1844Otherwise the binding must be done in an `HttpModule`:
1845
1846[source,java]
1847----
1848public class HttpModule extends HttpPluginModule {
1849
1850 @Override
1851 protected void configureServlets() {
1852 DynamicSet.bind(binder(), WebUiPlugin.class)
1853 .toInstance(new GwtPlugin("hello_gwt_plugin"));
1854 }
1855}
1856----
1857
1858The HTTP module above must be declared in the `pom.xml` for Maven
1859driven plugins:
1860
1861[source,xml]
1862----
1863<manifestEntries>
1864 <Gerrit-HttpModule>com.googlesource.gerrit.plugins.myplugin.HttpModule</Gerrit-HttpModule>
1865</manifestEntries>
1866----
1867
Shawn Pearcec8e96ad2013-12-09 08:20:44 -08001868The name that is provided to the `GwtPlugin` must match the GWT
1869module name compiled into the plugin. The name of the GWT module
1870can be explicitly set in the GWT module XML file by specifying
1871the `rename-to` attribute on the module. It is important that the
1872module name be unique across all plugins installed on the server,
1873as the module name determines the JavaScript namespace used by the
1874compiled plugin code.
Edwin Kempinb74daa92013-11-11 11:28:16 +01001875
1876[source,xml]
1877----
1878<module rename-to="hello_gwt_plugin">
1879----
1880
1881The actual GWT code must be implemented in a class that extends
Shawn Pearcec8e96ad2013-12-09 08:20:44 -08001882`com.google.gerrit.plugin.client.PluginEntryPoint`:
Edwin Kempinb74daa92013-11-11 11:28:16 +01001883
1884[source,java]
1885----
Shawn Pearcec8e96ad2013-12-09 08:20:44 -08001886public class HelloPlugin extends PluginEntryPoint {
Edwin Kempinb74daa92013-11-11 11:28:16 +01001887
1888 @Override
Shawn Pearcec8e96ad2013-12-09 08:20:44 -08001889 public void onPluginLoad() {
Edwin Kempinb74daa92013-11-11 11:28:16 +01001890 // Create the dialog box
1891 final DialogBox dialogBox = new DialogBox();
1892
1893 // The content of the dialog comes from a User specified Preference
1894 dialogBox.setText("Hello from GWT Gerrit UI plugin");
1895 dialogBox.setAnimationEnabled(true);
1896 Button closeButton = new Button("Close");
1897 VerticalPanel dialogVPanel = new VerticalPanel();
1898 dialogVPanel.setWidth("100%");
1899 dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
1900 dialogVPanel.add(closeButton);
1901
1902 closeButton.addClickHandler(new ClickHandler() {
1903 public void onClick(ClickEvent event) {
1904 dialogBox.hide();
1905 }
1906 });
1907
1908 // Set the contents of the Widget
1909 dialogBox.setWidget(dialogVPanel);
1910
1911 RootPanel rootPanel = RootPanel.get(HelloMenu.MENU_ID);
1912 rootPanel.getElement().removeAttribute("href");
1913 rootPanel.addDomHandler(new ClickHandler() {
1914 @Override
1915 public void onClick(ClickEvent event) {
1916 dialogBox.center();
1917 dialogBox.show();
1918 }
1919 }, ClickEvent.getType());
1920 }
1921}
1922----
1923
1924This class must be set as entry point in the GWT module:
1925
1926[source,xml]
1927----
1928<entry-point class="${package}.client.HelloPlugin"/>
1929----
1930
1931In addition this class must be defined as module in the `pom.xml` for the
1932`gwt-maven-plugin` and the `webappDirectory` option of `gwt-maven-plugin`
1933must be set to `${project.build.directory}/classes/static`:
1934
1935[source,xml]
1936----
1937<plugin>
1938 <groupId>org.codehaus.mojo</groupId>
1939 <artifactId>gwt-maven-plugin</artifactId>
David Pursehouse7ab81732015-05-07 12:00:47 +09001940 <version>2.7.0</version>
Edwin Kempinb74daa92013-11-11 11:28:16 +01001941 <configuration>
1942 <module>com.googlesource.gerrit.plugins.myplugin.HelloPlugin</module>
1943 <disableClassMetadata>true</disableClassMetadata>
1944 <disableCastChecking>true</disableCastChecking>
1945 <webappDirectory>${project.build.directory}/classes/static</webappDirectory>
1946 </configuration>
1947 <executions>
1948 <execution>
1949 <goals>
1950 <goal>compile</goal>
1951 </goals>
1952 </execution>
1953 </executions>
1954</plugin>
1955----
1956
1957To attach a GWT widget defined by the plugin to the Gerrit core UI
1958`com.google.gwt.user.client.ui.RootPanel` can be used to manipulate the
1959Gerrit core widgets:
1960
1961[source,java]
1962----
1963RootPanel rootPanel = RootPanel.get(HelloMenu.MENU_ID);
1964rootPanel.getElement().removeAttribute("href");
1965rootPanel.addDomHandler(new ClickHandler() {
1966 @Override
1967 public void onClick(ClickEvent event) {
1968 dialogBox.center();
1969 dialogBox.show();
1970 }
1971}, ClickEvent.getType());
1972----
1973
1974GWT plugins can come with their own css file. This css file must have a
1975unique name and must be registered in the GWT module:
1976
1977[source,xml]
1978----
1979<stylesheet src="hello.css"/>
1980----
1981
Edwin Kempin2570b102013-11-11 11:44:50 +01001982If a GWT plugin wants to invoke the Gerrit REST API it can use
David Pursehouse3a388312014-02-25 16:41:47 +09001983`com.google.gerrit.plugin.client.rpc.RestApi` to construct the URL
Edwin Kempin2570b102013-11-11 11:44:50 +01001984path and to trigger the REST calls.
1985
1986Example for invoking a Gerrit core REST endpoint:
1987
1988[source,java]
1989----
1990new RestApi("projects").id(projectName).view("description")
1991 .put("new description", new AsyncCallback<JavaScriptObject>() {
1992
1993 @Override
1994 public void onSuccess(JavaScriptObject result) {
1995 // TODO
1996 }
1997
1998 @Override
1999 public void onFailure(Throwable caught) {
2000 // never invoked
2001 }
2002});
2003----
2004
2005Example for invoking a REST endpoint defined by a plugin:
2006
2007[source,java]
2008----
2009new RestApi("projects").id(projectName).view("myplugin", "myview")
2010 .get(new AsyncCallback<JavaScriptObject>() {
2011
2012 @Override
2013 public void onSuccess(JavaScriptObject result) {
2014 // TODO
2015 }
2016
2017 @Override
2018 public void onFailure(Throwable caught) {
2019 // never invoked
2020 }
2021});
2022----
2023
2024The `onFailure(Throwable)` of the provided callback is never invoked.
2025If an error occurs, it is shown in an error dialog.
2026
2027In order to be able to do REST calls the GWT module must inherit
2028`com.google.gwt.json.JSON`:
2029
2030[source,xml]
2031----
2032<inherits name="com.google.gwt.json.JSON"/>
2033----
2034
Edwin Kempin15199792014-04-23 16:22:05 +02002035[[screen]]
Shawn Pearced5c844f2013-12-26 15:32:26 -08002036== Add Screen
Edwin Kempin15199792014-04-23 16:22:05 +02002037A link:#gwt_ui_extension[GWT plugin] can link:#top-menu-extensions[add
2038a menu item] that opens a screen that is implemented by the plugin.
2039This way plugin screens can be fully integrated into the Gerrit UI.
Shawn Pearced5c844f2013-12-26 15:32:26 -08002040
2041Example menu item:
2042[source,java]
2043----
2044public class MyMenu implements TopMenu {
2045 private final List<MenuEntry> menuEntries;
2046
2047 @Inject
2048 public MyMenu(@PluginName String name) {
David Pursehouseccdeae82016-05-03 23:16:15 +09002049 menuEntries = new ArrayList<>();
Shawn Pearced5c844f2013-12-26 15:32:26 -08002050 menuEntries.add(new MenuEntry("My Menu", Collections.singletonList(
2051 new MenuItem("My Screen", "#/x/" + name + "/my-screen", ""))));
2052 }
2053
2054 @Override
2055 public List<MenuEntry> getEntries() {
2056 return menuEntries;
2057 }
2058}
2059----
2060
2061Example screen:
2062[source,java]
2063----
2064public class MyPlugin extends PluginEntryPoint {
2065 @Override
2066 public void onPluginLoad() {
2067 Plugin.get().screen("my-screen", new Screen.EntryPoint() {
2068 @Override
2069 public void onLoad(Screen screen) {
2070 screen.add(new InlineLabel("My Screen");
2071 screen.show();
2072 }
2073 });
2074 }
2075}
2076----
2077
Edwin Kempin70e11122015-07-08 13:28:40 +02002078[[user-settings-screen]]
2079== Add User Settings Screen
2080
2081A link:#gwt_ui_extension[GWT plugin] can implement a user settings
2082screen that is integrated into the Gerrit user settings menu.
2083
2084Example settings screen:
2085[source,java]
2086----
2087public class MyPlugin extends PluginEntryPoint {
2088 @Override
2089 public void onPluginLoad() {
2090 Plugin.get().settingsScreen("my-preferences", "My Preferences",
2091 new Screen.EntryPoint() {
2092 @Override
2093 public void onLoad(Screen screen) {
2094 screen.setPageTitle("Settings");
2095 screen.add(new InlineLabel("My Preferences"));
2096 screen.show();
2097 }
2098 });
2099 }
2100}
2101----
2102
Edwin Kempinfa0d4942015-07-16 12:38:52 +02002103By defining an link:config-gerrit.html#urlAlias[urlAlias] Gerrit
2104administrators can map plugin screens into the Gerrit URL namespace or
2105even replace Gerrit screens by plugin screens.
2106
Edwin Kempinb1e6a3a2015-07-22 15:36:56 +02002107Plugins may also programatically add URL aliases in the preferences of
2108of a user. This way certain screens can be replaced for certain users.
2109E.g. the plugin may offer a user preferences setting for choosing a
2110screen that then sets/unsets a URL alias for the user.
2111
Edwin Kempin289f1a02014-02-04 16:08:25 +01002112[[settings-screen]]
2113== Plugin Settings Screen
2114
2115If a plugin implements a screen for administrating its settings that is
2116available under "#/x/<plugin-name>/settings" it is automatically linked
2117from the plugin list screen.
2118
Edwin Kempinf5a77332012-07-18 11:17:53 +02002119[[http]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002120== HTTP Servlets
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002121
2122Plugins or extensions may register additional HTTP servlets, and
2123wrap them with HTTP filters.
2124
2125Servlets may use auto-registration to declare the URL they handle:
2126
David Pursehouse68153d72013-09-04 10:09:17 +09002127[source,java]
2128----
2129import com.google.gerrit.extensions.annotations.Export;
2130import com.google.inject.Singleton;
2131import javax.servlet.http.HttpServlet;
2132import javax.servlet.http.HttpServletRequest;
2133import javax.servlet.http.HttpServletResponse;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002134
David Pursehouse68153d72013-09-04 10:09:17 +09002135@Export("/print")
2136@Singleton
2137class HelloServlet extends HttpServlet {
2138 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
2139 res.setContentType("text/plain");
2140 res.setCharacterEncoding("UTF-8");
2141 res.getWriter().write("Hello");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002142 }
David Pursehouse68153d72013-09-04 10:09:17 +09002143}
2144----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002145
Edwin Kempin8aa650f2012-07-18 11:25:48 +02002146The auto registration only works for standard servlet mappings like
Jonathan Nieder5758f182015-03-30 11:28:55 -07002147`/foo` or `+/foo/*+`. Regex style bindings must use a Guice ServletModule
Edwin Kempin8aa650f2012-07-18 11:25:48 +02002148to register the HTTP servlets and declare it explicitly in the manifest
2149with the `Gerrit-HttpModule` attribute:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002150
David Pursehouse68153d72013-09-04 10:09:17 +09002151[source,java]
2152----
2153import com.google.inject.servlet.ServletModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002154
David Pursehouse68153d72013-09-04 10:09:17 +09002155class MyWebUrls extends ServletModule {
2156 protected void configureServlets() {
2157 serve("/print").with(HelloServlet.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002158 }
David Pursehouse68153d72013-09-04 10:09:17 +09002159}
2160----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002161
2162For a plugin installed as name `helloworld`, the servlet implemented
2163by HelloServlet class will be available to users as:
2164
2165----
2166$ curl http://review.example.com/plugins/helloworld/print
2167----
Nasser Grainawie033b262012-05-09 17:54:21 -07002168
Edwin Kempinf5a77332012-07-18 11:17:53 +02002169[[data-directory]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002170== Data Directory
Edwin Kempin41f63912012-07-17 12:33:55 +02002171
Dave Borowitz9e158752015-02-24 10:17:04 -08002172Plugins can request a data directory with a `@PluginData` Path (or File,
2173deprecated) dependency. A data directory will be created automatically
2174by the server in `$site_path/data/$plugin_name` and passed to the
2175plugin.
Edwin Kempin41f63912012-07-17 12:33:55 +02002176
2177Plugins can use this to store any data they want.
2178
David Pursehouse68153d72013-09-04 10:09:17 +09002179[source,java]
2180----
2181@Inject
Dave Borowitz9e158752015-02-24 10:17:04 -08002182MyType(@PluginData java.nio.file.Path myDir) {
2183 this.in = Files.newInputStream(myDir.resolve("my.config"));
David Pursehouse68153d72013-09-04 10:09:17 +09002184}
2185----
Edwin Kempin41f63912012-07-17 12:33:55 +02002186
Dariusz Lukszaebab92a2014-09-10 11:14:19 +02002187[[secure-store]]
2188== SecureStore
2189
2190SecureStore allows to change the way Gerrit stores sensitive data like
2191passwords.
2192
2193In order to replace the default SecureStore (no-op) implementation,
2194a class that extends `com.google.gerrit.server.securestore.SecureStore`
2195needs to be provided (with dependencies) in a separate jar file. Then
2196link:pgm-SwitchSecureStore.html[SwitchSecureStore] must be run to
2197switch implementations.
2198
2199The SecureStore implementation is instantiated using a Guice injector
2200which binds the `File` annotated with the `@SitePath` annotation.
2201This means that a SecureStore implementation class can get access to
2202the `site_path` like in the following example:
2203
2204[source,java]
2205----
2206@Inject
2207MySecureStore(@SitePath java.io.File sitePath) {
2208 // your code
2209}
2210----
2211
2212No Guice bindings or modules are required. Gerrit will automatically
2213discover and bind the implementation.
2214
Michael Ochmann24612652016-02-12 17:26:18 +01002215[[accountcreation]]
2216== Account Creation
2217
2218Plugins can hook into the
2219link:rest-api-accounts.html#create-account[account creation] REST API and
2220inject additional external identifiers for an account that represents a user
2221in some external user store. For that, an implementation of the extension
2222point `com.google.gerrit.server.api.accounts.AccountExternalIdCreator`
2223must be registered.
2224
2225[source,java]
2226----
2227class MyExternalIdCreator implements AccountExternalIdCreator {
2228 @Override
2229 public List<AccountExternalId> create(Account.Id id, String username,
2230 String email) {
2231 // your code
2232 }
2233}
2234
2235bind(AccountExternalIdCreator.class)
2236 .annotatedWith(UniqueAnnotations.create())
2237 .to(MyExternalIdCreator.class);
2238}
2239----
2240
Edwin Kempinea621482013-10-16 12:58:24 +02002241[[download-commands]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002242== Download Commands
Edwin Kempinea621482013-10-16 12:58:24 +02002243
Edwin Kempineafde882015-05-11 15:40:44 +02002244Gerrit offers commands for downloading changes and cloning projects
2245using different download schemes (e.g. for downloading via different
2246network protocols). Plugins can contribute download schemes, download
2247commands and clone commands by implementing
2248`com.google.gerrit.extensions.config.DownloadScheme`,
2249`com.google.gerrit.extensions.config.DownloadCommand` and
2250`com.google.gerrit.extensions.config.CloneCommand`.
Edwin Kempinea621482013-10-16 12:58:24 +02002251
Edwin Kempineafde882015-05-11 15:40:44 +02002252The download schemes, download commands and clone commands which are
2253used most often are provided by the Gerrit core plugin
2254`download-commands`.
Edwin Kempinea621482013-10-16 12:58:24 +02002255
Edwin Kempin78279ba2015-05-22 15:22:41 +02002256[[included-in]]
2257== Included In
2258
2259For merged changes the link:user-review-ui.html#included-in[Included In]
2260drop-down panel shows the branches and tags in which the change is
2261included.
2262
2263Plugins can add additional systems in which the change can be included
2264by implementing `com.google.gerrit.extensions.config.ExternalIncludedIn`,
2265e.g. a plugin can provide a list of servers on which the change was
2266deployed.
2267
Sven Selbergae1a10c2014-02-14 14:24:29 +01002268[[links-to-external-tools]]
2269== Links To External Tools
2270
2271Gerrit has extension points that enables development of a
2272light-weight plugin that links commits to external
2273tools (GitBlit, CGit, company specific resources etc).
2274
2275PatchSetWebLinks will appear to the right of the commit-SHA1 in the UI.
2276
2277[source, java]
2278----
2279import com.google.gerrit.extensions.annotations.Listen;
2280import com.google.gerrit.extensions.webui.PatchSetWebLink;;
Sven Selberga85e64d2014-09-24 10:52:21 +02002281import com.google.gerrit.extensions.webui.WebLinkTarget;
Sven Selbergae1a10c2014-02-14 14:24:29 +01002282
2283@Listen
2284public class MyWeblinkPlugin implements PatchSetWebLink {
2285
2286 private String name = "MyLink";
2287 private String placeHolderUrlProjectCommit = "http://my.tool.com/project=%s/commit=%s";
Sven Selberg55484202014-06-26 08:48:51 +02002288 private String imageUrl = "http://placehold.it/16x16.gif";
Sven Selbergae1a10c2014-02-14 14:24:29 +01002289
2290 @Override
Jonathan Niederb3cd6902015-03-12 16:19:15 -07002291 public WebLinkInfo getPatchSetWebLink(String projectName, String commit) {
Sven Selberga85e64d2014-09-24 10:52:21 +02002292 return new WebLinkInfo(name,
2293 imageUrl,
2294 String.format(placeHolderUrlProjectCommit, project, commit),
2295 WebLinkTarget.BLANK);
Edwin Kempinceeed6b2014-09-11 17:07:33 +02002296 }
Sven Selbergae1a10c2014-02-14 14:24:29 +01002297}
2298----
2299
David Pursehouse58b8d762016-12-09 11:12:27 +09002300ParentWebLinks will appear to the right of the SHA1 of the parent
2301revisions in the UI. The implementation should in most use cases direct
2302to the same external service as PatchSetWebLink; it is provided as a
2303separate interface because not all users want to have links for the
2304parent revisions.
2305
Edwin Kempinb3696c82014-09-11 09:41:42 +02002306FileWebLinks will appear in the side-by-side diff screen on the right
2307side of the patch selection on each side.
2308
Edwin Kempin8cdce502014-12-06 10:55:38 +01002309DiffWebLinks will appear in the side-by-side and unified diff screen in
2310the header next to the navigation icons.
2311
Edwin Kempinea004752014-04-11 15:56:02 +02002312ProjectWebLinks will appear in the project list in the
2313`Repository Browser` column.
2314
Edwin Kempin0f697bd2014-09-10 18:23:29 +02002315BranchWebLinks will appear in the branch list in the last column.
2316
Edwin Kempinf82b8122016-06-03 09:20:16 +02002317FileHistoryWebLinks will appear on the access rights screen.
2318
Paladox none34da15c2017-07-01 14:49:10 +00002319TagWebLinks will appear in the tag list in the last column.
2320
Dave Borowitzd0c01fd2017-06-06 10:47:08 -04002321If a `get*WebLink` implementation returns `null`, the link will be omitted. This
2322allows the plugin to selectively "enable" itself on a per-project/branch/file
2323basis.
2324
Saša Živkovca7a67e2015-12-01 14:25:10 +01002325[[lfs-extension]]
2326== LFS Storage Plugins
2327
David Pursehouse2463c542016-08-02 16:04:58 +09002328Gerrit provides an extension point that enables development of
2329link:https://github.com/github/git-lfs/blob/master/docs/api/v1/http-v1-batch.md[
2330LFS (Large File Storage)] storage plugins. Gerrit core exposes the default LFS
2331protocol endpoint `<project-name>/info/lfs/objects/batch` and forwards the requests
2332to the configured link:config-gerrit.html#lfs[lfs.plugin] plugin which implements
2333the LFS protocol. By exposing the default LFS endpoint, the git-lfs client can be
2334used without any configuration.
Saša Živkovca7a67e2015-12-01 14:25:10 +01002335
2336[source, java]
2337----
2338/** Provide an LFS protocol implementation */
2339import org.eclipse.jgit.lfs.server.LargeFileRepository;
2340import org.eclipse.jgit.lfs.server.LfsProtocolServlet;
2341
2342@Singleton
2343public class LfsApiServlet extends LfsProtocolServlet {
2344 private static final long serialVersionUID = 1L;
2345
2346 private final S3LargeFileRepository repository;
2347
2348 @Inject
2349 LfsApiServlet(S3LargeFileRepository repository) {
2350 this.repository = repository;
2351 }
2352
2353 @Override
2354 protected LargeFileRepository getLargeFileRepository() {
2355 return repository;
2356 }
2357}
2358
2359/** Register the LfsApiServlet to listen on the default LFS protocol endpoint */
2360import static com.google.gerrit.httpd.plugins.LfsPluginServlet.URL_REGEX;
2361
2362import com.google.gerrit.httpd.plugins.HttpPluginModule;
2363
2364public class HttpModule extends HttpPluginModule {
2365
2366 @Override
2367 protected void configureServlets() {
2368 serveRegex(URL_REGEX).with(LfsApiServlet.class);
2369 }
2370}
2371
2372/** Provide an implementation of the LargeFileRepository */
2373import org.eclipse.jgit.lfs.server.s3.S3Repository;
2374
2375public class S3LargeFileRepository extends S3Repository {
2376...
2377}
2378----
2379
David Pursehouse8ad11732016-08-29 15:00:14 +09002380[[metrics]]
2381== Metrics
2382
2383=== Metrics Reporting
2384
2385To send Gerrit's metrics data to an external reporting backend, a plugin can
2386get a `MetricRegistry` injected and register an instance of a class that
2387implements the `Reporter` interface from link:http://metrics.dropwizard.io/[
2388DropWizard Metrics].
2389
2390Metric reporting plugin implementations are provided for
2391link:https://gerrit.googlesource.com/plugins/metrics-reporter-jmx/[JMX],
2392link:https://gerrit.googlesource.com/plugins/metrics-reporter-elasticsearch/[Elastic Search],
2393and link:https://gerrit.googlesource.com/plugins/metrics-reporter-graphite/[Graphite].
2394
2395There is also a working example of reporting metrics to the console in the
Eryk Szymanskida073b12017-09-11 14:27:57 +02002396link:https://gerrit.googlesource.com/plugins/cookbook-plugin/+/master/src/main/java/com/googlesource/gerrit/plugins/cookbook/ConsoleMetricReporter.java[
David Pursehouse8ad11732016-08-29 15:00:14 +09002397cookbook plugin].
2398
2399=== Providing own metrics
2400
2401Plugins may provide metrics to be dispatched to external reporting services by
2402getting a `MetricMaker` injected and creating instances of specific types of
2403metric:
2404
2405* Counter
2406+
2407Metric whose value increments during the life of the process.
2408
2409* Timer
2410+
2411Metric recording time spent on an operation.
2412
2413* Histogram
2414+
2415Metric recording statistical distribution (rate) of values.
2416
David Pursehouse48d05ea2017-02-03 19:05:29 +09002417Note that metrics cannot be recorded from plugin init steps that
2418are run during site initialization.
2419
David Pursehousec3bbd562017-02-06 20:25:29 +09002420By default, plugin metrics are recorded under
2421`plugins/${plugin-name}/${metric-name}`. This can be changed by
2422setting `plugins.${plugin-name}.metricsPrefix` in the `gerrit.config`
2423file. For example:
2424
2425----
2426 [plugin "my-plugin"]
2427 metricsPrefix = my-metrics
2428----
2429
2430will cause the metrics to be recorded under `my-metrics/${metric-name}`.
David Pursehouse8ad11732016-08-29 15:00:14 +09002431
2432See the replication metrics in the
2433link:https://gerrit.googlesource.com/plugins/replication/+/master/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationMetrics.java[
2434replication plugin] for an example of usage.
2435
Edwin Kempinda17bc32016-06-14 11:50:58 +02002436[[account-patch-review-store]]
2437== AccountPatchReviewStore
2438
2439The AccountPatchReviewStore is used to store reviewed flags on changes.
2440A reviewed flag is a tuple of (patch set ID, file, account ID) and
2441records whether the user has reviewed a file in a patch set. Each user
2442can easily have thousands of reviewed flags and the number of reviewed
2443flags is growing without bound. The store must be able handle this data
2444volume efficiently.
2445
2446Gerrit implements this extension point, but plugins may bind another
2447implementation, e.g. one that supports multi-master.
2448
2449----
2450DynamicItem.bind(binder(), AccountPatchReviewStore.class)
2451 .to(MultiMasterAccountPatchReviewStore.class);
2452
2453...
2454
2455public class MultiMasterAccountPatchReviewStore
2456 implements AccountPatchReviewStore {
2457 ...
2458}
2459----
2460
Dave Borowitzf1790ce2016-11-14 12:26:12 -08002461
Edwin Kempinf5a77332012-07-18 11:17:53 +02002462[[documentation]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002463== Documentation
Nasser Grainawie033b262012-05-09 17:54:21 -07002464
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002465If a plugin does not register a filter or servlet to handle URLs
Jonathan Nieder5758f182015-03-30 11:28:55 -07002466`+/Documentation/*+` or `+/static/*+`, the core Gerrit server will
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002467automatically export these resources over HTTP from the plugin JAR.
2468
David Pursehouse6853b5a2013-07-10 11:38:03 +09002469Static resources under the `static/` directory in the JAR will be
Dave Borowitzb893ac82013-03-27 10:03:55 -04002470available as `/plugins/helloworld/static/resource`. This prefix is
2471configurable by setting the `Gerrit-HttpStaticPrefix` attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002472
David Pursehouse6853b5a2013-07-10 11:38:03 +09002473Documentation files under the `Documentation/` directory in the JAR
Dave Borowitzb893ac82013-03-27 10:03:55 -04002474will be available as `/plugins/helloworld/Documentation/resource`. This
2475prefix is configurable by setting the `Gerrit-HttpDocumentationPrefix`
2476attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002477
Christian Aistleitner040cf822015-03-26 21:09:09 +01002478Documentation may be written in the Markdown flavor
2479link:https://github.com/sirthias/pegdown[pegdown]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002480if the file name ends with `.md`. Gerrit will automatically convert
2481Markdown to HTML if accessed with extension `.html`.
Nasser Grainawie033b262012-05-09 17:54:21 -07002482
Edwin Kempinf5a77332012-07-18 11:17:53 +02002483[[macros]]
Edwin Kempinc78777d2012-07-16 15:55:11 +02002484Within the Markdown documentation files macros can be used that allow
2485to write documentation with reasonably accurate examples that adjust
2486automatically based on the installation.
2487
2488The following macros are supported:
2489
2490[width="40%",options="header"]
2491|===================================================
2492|Macro | Replacement
2493|@PLUGIN@ | name of the plugin
2494|@URL@ | Gerrit Web URL
2495|@SSH_HOST@ | SSH Host
2496|@SSH_PORT@ | SSH Port
2497|===================================================
2498
2499The macros will be replaced when the documentation files are rendered
2500from Markdown to HTML.
2501
2502Macros that start with `\` such as `\@KEEP@` will render as `@KEEP@`
2503even if there is an expansion for `KEEP` in the future.
2504
Edwin Kempinf5a77332012-07-18 11:17:53 +02002505[[auto-index]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002506=== Automatic Index
Shawn O. Pearce795167c2012-05-12 11:20:18 -07002507
2508If a plugin does not handle its `/` URL itself, Gerrit will
2509redirect clients to the plugin's `/Documentation/index.html`.
2510Requests for `/Documentation/` (bare directory) will also redirect
2511to `/Documentation/index.html`.
2512
2513If neither resource `Documentation/index.html` or
2514`Documentation/index.md` exists in the plugin JAR, Gerrit will
2515automatically generate an index page for the plugin's documentation
2516tree by scanning every `*.md` and `*.html` file in the Documentation/
2517directory.
2518
2519For any discovered Markdown (`*.md`) file, Gerrit will parse the
2520header of the file and extract the first level one title. This
2521title text will be used as display text for a link to the HTML
2522version of the page.
2523
2524For any discovered HTML (`*.html`) file, Gerrit will use the name
2525of the file, minus the `*.html` extension, as the link text. Any
2526hyphens in the file name will be replaced with spaces.
2527
David Pursehouse6853b5a2013-07-10 11:38:03 +09002528If a discovered file is named `about.md` or `about.html`, its
2529content will be inserted in an 'About' section at the top of the
2530auto-generated index page. If both `about.md` and `about.html`
2531exist, only the first discovered file will be used.
2532
Shawn O. Pearce795167c2012-05-12 11:20:18 -07002533If a discovered file name beings with `cmd-` it will be clustered
David Pursehouse6853b5a2013-07-10 11:38:03 +09002534into a 'Commands' section of the generated index page.
2535
David Pursehousefe529152013-08-14 16:35:06 +09002536If a discovered file name beings with `servlet-` it will be clustered
2537into a 'Servlets' section of the generated index page.
2538
2539If a discovered file name beings with `rest-api-` it will be clustered
2540into a 'REST APIs' section of the generated index page.
2541
David Pursehouse6853b5a2013-07-10 11:38:03 +09002542All other files are clustered under a 'Documentation' section.
Shawn O. Pearce795167c2012-05-12 11:20:18 -07002543
2544Some optional information from the manifest is extracted and
2545displayed as part of the index page, if present in the manifest:
2546
2547[width="40%",options="header"]
2548|===================================================
2549|Field | Source Attribute
2550|Name | Implementation-Title
2551|Vendor | Implementation-Vendor
2552|Version | Implementation-Version
2553|URL | Implementation-URL
2554|API Version | Gerrit-ApiVersion
2555|===================================================
2556
Edwin Kempinf5a77332012-07-18 11:17:53 +02002557[[deployment]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002558== Deployment
Nasser Grainawie033b262012-05-09 17:54:21 -07002559
Edwin Kempinf7295742012-07-16 15:03:46 +02002560Compiled plugins and extensions can be deployed to a running Gerrit
2561server using the link:cmd-plugin-install.html[plugin install] command.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002562
David Pursehouse00c70812017-07-31 10:57:26 +01002563Web UI plugins distributed as a single `.js` file (or `.html' file for
2564Polygerrit) can be deployed without the overhead of JAR packaging. For
2565more information refer to link:cmd-plugin-install.html[plugin install]
2566command.
Dariusz Luksza357a2422012-11-12 06:16:26 +01002567
David Pursehouse75021fe2017-07-31 23:07:04 +02002568Plugins can also be copied directly into the server's directory at
2569`$site_path/plugins/$name.(jar|js|html)`. For Web UI plugins, the name
2570of the file, minus the `.js` or `.html` extension, will be used as the
2571plugin name. For JAR plugins, the value of the `Gerrit-PluginName`
2572manifest attribute will be used, if provided, otherwise the name of
2573the file, minus the `.jar` extension, will be used.
2574
2575For Web UI plugins, the plugin version is derived from the filename.
2576If the filename contains one or more hyphens, the version is taken
2577from the portion following the last hyphen. For example if the plugin
2578filename is `my-plugin-1.0.js` the version will be `1.0`. For JAR
2579plugins, the version is taken from the `Version` attribute in the
2580manifest.
2581
2582Unless disabled, servers periodically scan the `$site_path/plugins`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002583directory for updated plugins. The time can be adjusted by
2584link:config-gerrit.html#plugins.checkFrequency[plugins.checkFrequency].
Deniz Türkoglueb78b602012-05-07 14:02:36 -07002585
Edwin Kempinf7295742012-07-16 15:03:46 +02002586For disabling plugins the link:cmd-plugin-remove.html[plugin remove]
2587command can be used.
2588
Brad Larsond5e87c32012-07-11 12:18:49 -05002589Disabled plugins can be re-enabled using the
2590link:cmd-plugin-enable.html[plugin enable] command.
2591
Edwin Kempinc1a25102015-06-22 14:47:36 +02002592== Known issues and bugs
2593
2594=== Error handling in UI when using the REST API
2595
2596When a plugin invokes a REST endpoint in the UI, it provides an
2597`AsyncCallback` to handle the result. At the moment the
2598`onFailure(Throwable)` of the callback is never invoked, even if there
2599is an error. Errors are always handled by the Gerrit core UI which
2600shows the error dialog. This means currently plugins cannot do any
2601error handling and e.g. ignore expected errors.
2602
Han-Wen Nienhuys84d830b2017-02-15 16:36:04 +01002603In the following example the REST endpoint would return '404 Not
2604Found' if the user has no username and the Gerrit core UI would
2605display an error dialog for this. However having no username is
2606not an error and the plugin may like to handle this case.
Edwin Kempinc1a25102015-06-22 14:47:36 +02002607
2608[source,java]
2609----
Han-Wen Nienhuys84d830b2017-02-15 16:36:04 +01002610new RestApi("accounts").id("self").view("username")
Edwin Kempinc1a25102015-06-22 14:47:36 +02002611 .get(new AsyncCallback<NativeString>() {
2612
2613 @Override
Han-Wen Nienhuys84d830b2017-02-15 16:36:04 +01002614 public void onSuccess(NativeString username) {
Edwin Kempinc1a25102015-06-22 14:47:36 +02002615 // TODO
2616 }
2617
2618 @Override
2619 public void onFailure(Throwable caught) {
2620 // never invoked
2621 }
2622});
2623----
2624
2625
Patrick Hiesel87880b02016-05-03 18:15:08 +02002626[[reviewer-suggestion]]
2627== Reviewer Suggestion Plugins
2628
2629Gerrit provides an extension point that enables Plugins to rank
2630the list of reviewer suggestion a user receives upon clicking "Add Reviewer" on
2631the change screen.
2632Gerrit supports both a default suggestion that appears when the user has not yet
2633typed anything and a filtered suggestion that is shown as the user starts
2634typing.
2635Plugins receive a candidate list and can return a Set of suggested reviewers
2636containing the Account.Id and a score for each reviewer.
2637The candidate list is non-binding and plugins can choose to return reviewers not
2638initially contained in the candidate list.
2639Server administrators can configure the overall weight of each plugin using the
2640weight config parameter on [addreviewer "<pluginName-exportName>"].
2641
2642[source, java]
2643----
Patrick Hiesel2514e412016-10-13 09:34:44 +02002644import com.google.gerrit.common.Nullable;
2645import com.google.gerrit.extensions.annotations.ExtensionPoint;
Patrick Hiesel87880b02016-05-03 18:15:08 +02002646import com.google.gerrit.reviewdb.client.Account;
2647import com.google.gerrit.reviewdb.client.Change;
Patrick Hiesel2514e412016-10-13 09:34:44 +02002648import com.google.gerrit.reviewdb.client.Project;
Patrick Hiesel87880b02016-05-03 18:15:08 +02002649
2650import java.util.Set;
2651
2652public class MyPlugin implements ReviewerSuggestion {
Patrick Hiesel2514e412016-10-13 09:34:44 +02002653 public Set<SuggestedReviewer> suggestReviewers(Project.NameKey project,
2654 @Nullable Change.Id changeId, @Nullable String query,
2655 Set<Account.Id> candidates) {
2656 Set<SuggestedReviewer> suggestions = new HashSet<>();
2657 // Implement your ranking logic here
2658 return suggestions;
Patrick Hiesel87880b02016-05-03 18:15:08 +02002659 }
2660}
2661----
2662
2663
Patrick Hiesel30c76182017-01-20 11:46:43 +01002664[[mail-filter]]
2665== Mail Filter Plugins
2666
2667Gerrit provides an extension point that enables Plugins to discard incoming
2668messages and prevent further processing by Gerrit.
2669
David Pursehouse4b067752017-03-03 15:54:53 +09002670This can be used to implement spam checks, signature validations or organization
Patrick Hiesel30c76182017-01-20 11:46:43 +01002671specific checks like IP filters.
2672
2673[source, java]
2674----
2675import com.google.gerrit.extensions.annotations.ExtensionPoint;
2676import com.google.gerrit.server.mail.receive.MailMessage;
2677
2678public class MyPlugin implements MailFilter {
2679 boolean shouldProcessMessage(MailMessage message) {
2680 // Implement your filter logic here
2681 return true;
2682 }
2683}
2684----
2685
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002686== SEE ALSO
David Ostrovskyf86bae52013-09-01 09:10:39 +02002687
2688* link:js-api.html[JavaScript API]
2689* link:dev-rest-api.html[REST API Developers' Notes]
2690
Deniz Türkoglueb78b602012-05-07 14:02:36 -07002691GERRIT
2692------
2693Part of link:index.html[Gerrit Code Review]
Yuxuan 'fishy' Wang99cb68d2013-10-31 17:26:00 -07002694
2695SEARCHBOX
2696---------