blob: f7028e00db6e379fecf852cf9d2cbb73f1726acc [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
Edwin Kempin19e89c82017-10-11 12:00:51 +0200486[[merge-super-set-computation]]
487== Merge Super Set Computation
488
489The algorithm to compute the merge super set to detect changes that
490should be submitted together can be customized by implementing
491`com.google.gerrit.server.git.MergeSuperSetComputation`.
492MergeSuperSetComputation is a DynamicItem, so Gerrit may only have one
493implementation.
494
Saša Živkovec85a072014-01-28 10:08:25 +0100495[[receive-pack]]
496== Receive Pack Initializers
497
Dave Borowitzb8a2bae2017-10-03 10:34:28 +0100498Plugins may provide ReceivePackInitializer instances, which will be
499invoked by Gerrit just before a ReceivePack instance will be used.
500Usually, plugins will make use of the setXXX methods on the ReceivePack
501to set additional properties on it.
502
503The interactions with the core Gerrit ReceivePack initialization and
504between ReceivePackInitializers can be complex. Please read the
505ReceivePack Javadoc and Gerrit AsyncReceiveCommits implementation
506carefully.
Saša Živkovec85a072014-01-28 10:08:25 +0100507
Saša Živkov626c7312014-02-24 17:15:08 +0100508[[post-receive-hook]]
509== Post Receive-Pack Hooks
510
511Plugins may register PostReceiveHook instances in order to get
512notified when JGit successfully receives a pack. This may be useful
513for those plugins which would like to monitor changes in Git
514repositories.
515
Dave Borowitz223580f2017-10-03 09:55:51 +0100516[[upload-pack]]
517== Upload Pack Initializers
518
519Plugins may provide UploadPackInitializer instances, which will be
520invoked by Gerrit just before a UploadPack instance will be used.
521Usually, plugins will make use of the setXXX methods on the UploadPack
522to set additional properties on it.
523
524The interactions with the core Gerrit UploadPack initialization and
525between UploadPackInitializers can be complex. Please read the
526UploadPack Javadoc and Gerrit Upload/UploadFactory implementations
527carefully.
528
Hugo Arès572d5422014-06-17 14:22:03 -0400529[[pre-upload-hook]]
530== Pre Upload-Pack Hooks
531
532Plugins may register PreUploadHook instances in order to get
533notified when JGit is about to upload a pack. This may be useful
534for those plugins which would like to monitor usage in Git
535repositories.
536
Hugo Arès41b4c0d2016-08-02 15:26:57 -0400537[[post-upload-hook]]
538== Post Upload-Pack Hooks
539
540Plugins may register PostUploadHook instances in order to get notified after
541JGit is done uploading a pack.
542
Edwin Kempinf5a77332012-07-18 11:17:53 +0200543[[ssh]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800544== SSH Commands
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700545
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700546Plugins may provide commands that can be accessed through the SSH
547interface (extensions do not have this option).
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700548
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700549Command implementations must extend the base class SshCommand:
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700550
David Pursehouse68153d72013-09-04 10:09:17 +0900551[source,java]
552----
553import com.google.gerrit.sshd.SshCommand;
David Ostrovskyb7d97752013-11-09 05:23:26 +0100554import com.google.gerrit.sshd.CommandMetaData;
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700555
Ian Bulle1a12202014-02-16 17:15:42 -0800556@CommandMetaData(name="print", description="Print hello command")
David Pursehouse68153d72013-09-04 10:09:17 +0900557class PrintHello extends SshCommand {
Ian Bulle1a12202014-02-16 17:15:42 -0800558 @Override
559 protected void run() {
David Pursehouse68153d72013-09-04 10:09:17 +0900560 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700561 }
David Pursehouse68153d72013-09-04 10:09:17 +0900562}
563----
Nasser Grainawie033b262012-05-09 17:54:21 -0700564
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700565If no Guice modules are declared in the manifest, SSH commands may
Edwin Kempin948de0f2012-07-16 10:34:35 +0200566use auto-registration by providing an `@Export` annotation:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700567
David Pursehouse68153d72013-09-04 10:09:17 +0900568[source,java]
569----
570import com.google.gerrit.extensions.annotations.Export;
571import com.google.gerrit.sshd.SshCommand;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700572
David Pursehouse68153d72013-09-04 10:09:17 +0900573@Export("print")
574class PrintHello extends SshCommand {
Ian Bulle1a12202014-02-16 17:15:42 -0800575 @Override
576 protected void run() {
David Pursehouse68153d72013-09-04 10:09:17 +0900577 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700578 }
David Pursehouse68153d72013-09-04 10:09:17 +0900579}
580----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700581
582If explicit registration is being used, a Guice module must be
583supplied to register the SSH command and declared in the manifest
584with the `Gerrit-SshModule` attribute:
585
David Pursehouse68153d72013-09-04 10:09:17 +0900586[source,java]
587----
588import com.google.gerrit.sshd.PluginCommandModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700589
David Pursehouse68153d72013-09-04 10:09:17 +0900590class MyCommands extends PluginCommandModule {
Ian Bulle1a12202014-02-16 17:15:42 -0800591 @Override
David Pursehouse68153d72013-09-04 10:09:17 +0900592 protected void configureCommands() {
David Ostrovskyb7d97752013-11-09 05:23:26 +0100593 command(PrintHello.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700594 }
David Pursehouse68153d72013-09-04 10:09:17 +0900595}
596----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700597
598For a plugin installed as name `helloworld`, the command implemented
599by PrintHello class will be available to users as:
600
601----
Keunhong Parka09a6f12012-07-10 14:45:02 -0600602$ ssh -p 29418 review.example.com helloworld print
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700603----
604
David Ostrovsky79c4d892014-03-15 13:52:46 +0100605[[multiple-commands]]
606=== Multiple Commands bound to one implementation
607
David Ostrovskye3172b32013-10-13 14:19:13 +0200608Multiple SSH commands can be bound to the same implementation class. For
609example a Gerrit Shell plugin can bind different shell commands to the same
610implementation class:
611
612[source,java]
613----
614public class SshShellModule extends PluginCommandModule {
615 @Override
616 protected void configureCommands() {
617 command("ls").to(ShellCommand.class);
618 command("ps").to(ShellCommand.class);
619 [...]
620 }
621}
622----
623
624With the possible implementation:
625
626[source,java]
627----
628public class ShellCommand extends SshCommand {
629 @Override
630 protected void run() throws UnloggedFailure {
631 String cmd = getName().substring(getPluginName().length() + 1);
632 ProcessBuilder proc = new ProcessBuilder(cmd);
633 Process cmd = proc.start();
634 [...]
635 }
636}
637----
638
639And the call:
640
641----
642$ ssh -p 29418 review.example.com shell ls
643$ ssh -p 29418 review.example.com shell ps
644----
645
David Ostrovsky79c4d892014-03-15 13:52:46 +0100646[[root-level-commands]]
647=== Root Level Commands
648
David Ostrovskyb7d97752013-11-09 05:23:26 +0100649Single command plugins are also supported. In this scenario plugin binds
650SSH command to its own name. `SshModule` must inherit from
651`SingleCommandPluginModule` class:
652
653[source,java]
654----
655public class SshModule extends SingleCommandPluginModule {
656 @Override
657 protected void configure(LinkedBindingBuilder<Command> b) {
658 b.to(ShellCommand.class);
659 }
660}
661----
662
663If the plugin above is deployed under sh.jar file in `$site/plugins`
David Pursehouse659860f2013-12-16 14:50:04 +0900664directory, generic commands can be called without specifying the
David Ostrovskyb7d97752013-11-09 05:23:26 +0100665actual SSH command. Note in the example below, that the called commands
666`ls` and `ps` was not explicitly bound:
667
668----
669$ ssh -p 29418 review.example.com sh ls
670$ ssh -p 29418 review.example.com sh ps
671----
672
Martin Fick5f6222912015-11-12 14:52:50 -0700673[[search_operators]]
Edwin Kempin4b479772016-11-14 14:34:33 -0800674== Search Operators
Martin Fick5f6222912015-11-12 14:52:50 -0700675
676Plugins can define new search operators to extend change searching by
677implementing the `ChangeQueryBuilder.ChangeOperatorFactory` interface
678and registering it to an operator name in the plugin module's
679`configure()` method. The search operator name is defined during
680registration via the DynamicMap annotation mechanism. The plugin
681name will get appended to the annotated name, with an underscore
682in between, leading to the final operator name. An example
683registration looks like this:
684
685 bind(ChangeOperatorFactory.class)
686 .annotatedWith(Exports.named("sample"))
687 .to(SampleOperator.class);
688
689If this is registered in the `myplugin` plugin, then the resulting
690operator will be named `sample_myplugin`.
691
692The search operator itself is implemented by ensuring that the
693`create()` method of the class implementing the
694`ChangeQueryBuilder.ChangeOperatorFactory` interface returns a
695`Predicate<ChangeData>`. Here is a sample operator factory
David Pursehousea61ee502016-09-06 16:27:09 +0900696definition which creates a `MyPredicate`:
Martin Fick5f6222912015-11-12 14:52:50 -0700697
698[source,java]
699----
700@Singleton
701public class SampleOperator
702 implements ChangeQueryBuilder.ChangeOperatorFactory {
Edwin Kempincc82b242016-06-28 10:00:53 +0200703 public static class MyPredicate extends OperatorChangePredicate<ChangeData> {
Martin Fick5f6222912015-11-12 14:52:50 -0700704 ...
705 }
706
707 @Override
708 public Predicate<ChangeData> create(ChangeQueryBuilder builder, String value)
709 throws QueryParseException {
710 return new MyPredicate(value);
711 }
712}
713----
714
Craig Chapeldba4e892016-11-14 09:25:17 -0700715[[search_operands]]
716=== Search Operands ===
717
718Plugins can define new search operands to extend change searching.
719Plugin methods implementing search operands (returning a
720`Predicate<ChangeData>`), must be defined on a class implementing
721one of the `ChangeQueryBuilder.ChangeOperandsFactory` interfaces
722(.e.g., ChangeQueryBuilder.ChangeHasOperandFactory). The specific
723`ChangeOperandFactory` class must also be bound to the `DynamicSet` from
724a module's `configure()` method in the plugin.
725
726The new operand, when used in a search would appear as:
727 operatorName:operandName_pluginName
728
729A sample `ChangeHasOperandFactory` class implementing, and registering, a
730new `has:sample_pluginName` operand is shown below:
731
732====
733 @Singleton
734 public class SampleHasOperand implements ChangeHasOperandFactory {
735 public static class Module extends AbstractModule {
736 @Override
737 protected void configure() {
738 bind(ChangeHasOperandFactory.class)
739 .annotatedWith(Exports.named("sample")
740 .to(SampleHasOperand.class);
741 }
742 }
743
744 @Override
745 public Predicate<ChangeData> create(ChangeQueryBuilder builder)
746 throws QueryParseException {
747 return new HasSamplePredicate();
748 }
749====
750
Zac Livingston4f083a82016-05-20 12:38:43 -0600751[[command_options]]
752=== Command Options ===
753
754Plugins can provide additional options for each of the gerrit ssh and the
755REST API commands by implementing the DynamicBean interface and registering
756it to a command class name in the plugin module's `configure()` method. The
757plugin's name will be prepended to the name of each @Option annotation found
758on the DynamicBean object provided by the plugin. The example below shows a
759plugin that adds an option to log a value from the gerrit 'ban-commits'
760ssh command.
761
762[source, java]
763----
764public class SshModule extends AbstractModule {
765 private static final Logger log = LoggerFactory.getLogger(SshModule.class);
766
767 @Override
768 protected void configure() {
769 bind(DynamicOptions.DynamicBean.class)
770 .annotatedWith(Exports.named(
771 com.google.gerrit.sshd.commands.BanCommitCommand.class))
772 .to(BanOptions.class);
773 }
774
775 public static class BanOptions implements DynamicOptions.DynamicBean {
776 @Option(name = "--log", aliases = { "-l" }, usage = "Say Hello in the Log")
777 private void parse(String arg) {
778 log.error("Say Hello in the Log " + arg);
779 }
780 }
781----
Craig Chapeldba4e892016-11-14 09:25:17 -0700782
Zac Livingstoncffb24592016-11-13 09:08:08 -0700783[[query_attributes]]
784=== Query Attributes ===
785
786Plugins can provide additional attributes to be returned in Gerrit queries by
787implementing the ChangeAttributeFactory interface and registering it to the
788ChangeQueryProcessor.ChangeAttributeFactory class in the plugin module's
789'configure()' method. The new attribute(s) will be output under a "plugin"
790attribute in the change query output.
791
792The example below shows a plugin that adds two attributes ('exampleName' and
793'changeValue'), to the change query output.
794
795[source, java]
796----
797public class Module extends AbstractModule {
798 @Override
799 protected void configure() {
800 bind(ChangeAttributeFactory.class)
801 .annotatedWith(Exports.named("example"))
802 .to(AttributeFactory.class);
803 }
804}
805
806public class AttributeFactory implements ChangeAttributeFactory {
807
808 public class PluginAttribute extends PluginDefinedInfo {
809 public String exampleName;
810 public String changeValue;
811
812 public PluginAttribute(ChangeData c) {
813 this.exampleName = "Attribute Example";
814 this.changeValue = Integer.toString(c.getId().get());
815 }
816 }
817
818 @Override
819 public PluginDefinedInfo create(ChangeData c, ChangeQueryProcessor qp, String plugin) {
820 return new PluginAttribute(c);
821 }
822}
823----
824
825Example
826----
827
828ssh -p 29418 localhost gerrit query "change:1" --format json
829
830Output:
831
832{
833 "url" : "http://localhost:8080/1",
834 "plugins" : [
835 {
836 "name" : "myplugin-name",
837 "exampleName" : "Attribute Example",
838 "changeValue" : "1"
839 }
840 ],
841 ...
842}
843----
844
Edwin Kempin78ca0942013-10-30 11:24:06 +0100845[[simple-configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800846== Simple Configuration in `gerrit.config`
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200847
848In Gerrit, global configuration is stored in the `gerrit.config` file.
849If a plugin needs global configuration, this configuration should be
850stored in a `plugin` subsection in the `gerrit.config` file.
851
Edwin Kempinc9b68602013-10-30 09:32:43 +0100852This approach of storing the plugin configuration is only suitable for
853plugins that have a simple configuration that only consists of
854key-value pairs. With this approach it is not possible to have
855subsections in the plugin configuration. Plugins that require a complex
Edwin Kempin78ca0942013-10-30 11:24:06 +0100856configuration need to store their configuration in their
857link:#configuration[own configuration file] where they can make use of
858subsections. On the other hand storing the plugin configuration in a
859'plugin' subsection in the `gerrit.config` file has the advantage that
860administrators have all configuration parameters in one file, instead
861of having one configuration file per plugin.
Edwin Kempinc9b68602013-10-30 09:32:43 +0100862
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200863To avoid conflicts with other plugins, it is recommended that plugins
864only use the `plugin` subsection with their own name. For example the
865`helloworld` plugin should store its configuration in the
866`plugin.helloworld` subsection:
867
868----
869[plugin "helloworld"]
870 language = Latin
871----
872
Sasa Zivkovacdf5332013-09-20 14:05:15 +0200873Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200874plugin can easily access its configuration and there is no need for a
875plugin to parse the `gerrit.config` file on its own:
876
877[source,java]
878----
David Pursehouse529ec252013-09-27 13:45:14 +0900879@Inject
880private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200881
David Pursehoused128c892013-10-22 21:52:21 +0900882[...]
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200883
Edwin Kempin122622d2013-10-29 16:45:44 +0100884String language = cfg.getFromGerritConfig("helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +0900885 .getString("language", "English");
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200886----
887
Edwin Kempin78ca0942013-10-30 11:24:06 +0100888[[configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800889== Configuration in own config file
Edwin Kempin78ca0942013-10-30 11:24:06 +0100890
891Plugins can store their configuration in an own configuration file.
892This makes sense if the plugin configuration is rather complex and
893requires the usage of subsections. Plugins that have a simple
894key-value pair configuration can store their configuration in a
895link:#simple-configuration[`plugin` subsection of the `gerrit.config`
896file].
897
898The plugin configuration file must be named after the plugin and must
899be located in the `etc` folder of the review site. For example a
900configuration file for a `default-reviewer` plugin could look like
901this:
902
903.$site_path/etc/default-reviewer.config
904----
905[branch "refs/heads/master"]
906 reviewer = Project Owners
907 reviewer = john.doe@example.com
908[match "file:^.*\.txt"]
909 reviewer = My Info Developers
910----
911
David Pursehouse5b47bc42016-07-22 11:00:25 +0900912Plugins that have sensitive configuration settings can store those settings in
913an own secure configuration file. The plugin's secure configuration file must be
914named after the plugin and must be located in the `etc` folder of the review
915site. For example a secure configuration file for a `default-reviewer` plugin
916could look like this:
917
918.$site_path/etc/default-reviewer.secure.config
919----
920[auth]
921 password = secret
922----
923
Edwin Kempin78ca0942013-10-30 11:24:06 +0100924Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
925plugin can easily access its configuration:
926
927[source,java]
928----
929@Inject
930private com.google.gerrit.server.config.PluginConfigFactory cfg;
931
932[...]
933
934String[] reviewers = cfg.getGlobalPluginConfig("default-reviewer")
935 .getStringList("branch", "refs/heads/master", "reviewer");
David Pursehouse5b47bc42016-07-22 11:00:25 +0900936String password = cfg.getGlobalPluginConfig("default-reviewer")
937 .getString("auth", null, "password");
Edwin Kempin78ca0942013-10-30 11:24:06 +0100938----
939
Edwin Kempin78ca0942013-10-30 11:24:06 +0100940
Edwin Kempin705f2842013-10-30 14:25:31 +0100941[[simple-project-specific-configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800942== Simple Project Specific Configuration in `project.config`
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200943
944In Gerrit, project specific configuration is stored in the project's
945`project.config` file on the `refs/meta/config` branch. If a plugin
946needs configuration on project level (e.g. to enable its functionality
947only for certain projects), this configuration should be stored in a
948`plugin` subsection in the project's `project.config` file.
949
Edwin Kempinc9b68602013-10-30 09:32:43 +0100950This approach of storing the plugin configuration is only suitable for
951plugins that have a simple configuration that only consists of
952key-value pairs. With this approach it is not possible to have
953subsections in the plugin configuration. Plugins that require a complex
Edwin Kempin705f2842013-10-30 14:25:31 +0100954configuration need to store their configuration in their
955link:#project-specific-configuration[own configuration file] where they
956can make use of subsections. On the other hand storing the plugin
957configuration in a 'plugin' subsection in the `project.config` file has
958the advantage that project owners have all configuration parameters in
959one file, instead of having one configuration file per plugin.
Edwin Kempinc9b68602013-10-30 09:32:43 +0100960
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200961To avoid conflicts with other plugins, it is recommended that plugins
962only use the `plugin` subsection with their own name. For example the
963`helloworld` plugin should store its configuration in the
964`plugin.helloworld` subsection:
965
966----
967 [plugin "helloworld"]
968 enabled = true
969----
970
971Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
972plugin can easily access its project specific configuration and there
973is no need for a plugin to parse the `project.config` file on its own:
974
975[source,java]
976----
David Pursehouse529ec252013-09-27 13:45:14 +0900977@Inject
978private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200979
David Pursehoused128c892013-10-22 21:52:21 +0900980[...]
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200981
Edwin Kempin122622d2013-10-29 16:45:44 +0100982boolean enabled = cfg.getFromProjectConfig(project, "helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +0900983 .getBoolean("enabled", false);
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200984----
985
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200986It is also possible to get missing configuration parameters inherited
987from the parent projects:
988
989[source,java]
990----
David Pursehouse529ec252013-09-27 13:45:14 +0900991@Inject
992private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200993
David Pursehoused128c892013-10-22 21:52:21 +0900994[...]
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200995
Edwin Kempin122622d2013-10-29 16:45:44 +0100996boolean enabled = cfg.getFromProjectConfigWithInheritance(project, "helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +0900997 .getBoolean("enabled", false);
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200998----
999
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +02001000Project owners can edit the project configuration by fetching the
1001`refs/meta/config` branch, editing the `project.config` file and
1002pushing the commit back.
1003
Edwin Kempin9ce4f552013-11-15 16:00:00 +01001004Plugin configuration values that are stored in the `project.config`
1005file can be exposed in the ProjectInfoScreen to allow project owners
1006to see and edit them from the UI.
1007
1008For this an instance of `ProjectConfigEntry` needs to be bound for each
1009parameter. The export name must be a valid Git variable name. The
1010variable name is case-insensitive, allows only alphanumeric characters
1011and '-', and must start with an alphabetic character.
1012
Edwin Kempina6c1c452013-11-28 16:55:22 +01001013The example below shows how the parameters `plugin.helloworld.enabled`
1014and `plugin.helloworld.language` are bound to be editable from the
David Pursehousea1d633b2014-05-02 17:21:02 +09001015Web UI. For the parameter `plugin.helloworld.enabled` "Enable Greeting"
Edwin Kempina6c1c452013-11-28 16:55:22 +01001016is provided as display name and the default value is set to `true`.
1017For the parameter `plugin.helloworld.language` "Preferred Language"
1018is provided as display name and "en" is set as default value.
Edwin Kempin9ce4f552013-11-15 16:00:00 +01001019
1020[source,java]
1021----
1022class Module extends AbstractModule {
1023 @Override
1024 protected void configure() {
1025 bind(ProjectConfigEntry.class)
Edwin Kempina6c1c452013-11-28 16:55:22 +01001026 .annotatedWith(Exports.named("enabled"))
1027 .toInstance(new ProjectConfigEntry("Enable Greeting", true));
1028 bind(ProjectConfigEntry.class)
Edwin Kempin9ce4f552013-11-15 16:00:00 +01001029 .annotatedWith(Exports.named("language"))
1030 .toInstance(new ProjectConfigEntry("Preferred Language", "en"));
1031 }
1032}
1033----
1034
Edwin Kempinb64d3972013-11-17 18:55:48 +01001035By overwriting the `onUpdate` method of `ProjectConfigEntry` plugins
1036can be notified when this configuration parameter is updated on a
1037project.
1038
Janice Agustine5a9d012015-08-24 09:05:56 -04001039[[configuring-groups]]
1040=== Referencing groups in `project.config`
1041
1042Plugins can refer to groups so that when they are renamed, the project
1043config will also be updated in this section. The proper format to use is
Hugo Arès532e0a32017-06-16 09:31:08 -04001044the same as for any other group reference in the `project.config`, as shown below.
Janice Agustine5a9d012015-08-24 09:05:56 -04001045
1046----
Hugo Arès532e0a32017-06-16 09:31:08 -04001047group group_name
Janice Agustine5a9d012015-08-24 09:05:56 -04001048----
1049
Hugo Arès532e0a32017-06-16 09:31:08 -04001050The file `groups` must also contains the mapping of the group name and its UUID,
1051refer to link:config-project-config.html#file-groups[file groups]
1052
Edwin Kempin705f2842013-10-30 14:25:31 +01001053[[project-specific-configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001054== Project Specific Configuration in own config file
Edwin Kempin705f2842013-10-30 14:25:31 +01001055
1056Plugins can store their project specific configuration in an own
1057configuration file in the projects `refs/meta/config` branch.
1058This makes sense if the plugins project specific configuration is
1059rather complex and requires the usage of subsections. Plugins that
1060have a simple key-value pair configuration can store their project
1061specific configuration in a link:#simple-project-specific-configuration[
1062`plugin` subsection of the `project.config` file].
1063
1064The plugin configuration file in the `refs/meta/config` branch must be
1065named after the plugin. For example a configuration file for a
1066`default-reviewer` plugin could look like this:
1067
1068.default-reviewer.config
1069----
1070[branch "refs/heads/master"]
1071 reviewer = Project Owners
1072 reviewer = john.doe@example.com
1073[match "file:^.*\.txt"]
1074 reviewer = My Info Developers
1075----
1076
1077Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
1078plugin can easily access its project specific configuration:
1079
1080[source,java]
1081----
1082@Inject
1083private com.google.gerrit.server.config.PluginConfigFactory cfg;
1084
1085[...]
1086
1087String[] reviewers = cfg.getProjectPluginConfig(project, "default-reviewer")
1088 .getStringList("branch", "refs/heads/master", "reviewer");
1089----
1090
Edwin Kempin762da382013-10-30 14:50:01 +01001091It is also possible to get missing configuration parameters inherited
1092from the parent projects:
1093
1094[source,java]
1095----
1096@Inject
1097private com.google.gerrit.server.config.PluginConfigFactory cfg;
1098
1099[...]
1100
David Ostrovsky468e4c32014-03-22 06:05:35 -07001101String[] reviewers = cfg.getProjectPluginConfigWithInheritance(project, "default-reviewer")
Edwin Kempin762da382013-10-30 14:50:01 +01001102 .getStringList("branch", "refs/heads/master", "reviewer");
1103----
1104
Edwin Kempin705f2842013-10-30 14:25:31 +01001105Project owners can edit the project configuration by fetching the
1106`refs/meta/config` branch, editing the `<plugin-name>.config` file and
1107pushing the commit back.
1108
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001109== React on changes in project configuration
Edwin Kempina46b6c92013-12-04 21:05:24 +01001110
1111If a plugin wants to react on changes in the project configuration, it
1112can implement a `GitReferenceUpdatedListener` and filter on events for
1113the `refs/meta/config` branch:
1114
1115[source,java]
1116----
1117public class MyListener implements GitReferenceUpdatedListener {
1118
1119 private final MetaDataUpdate.Server metaDataUpdateFactory;
1120
1121 @Inject
1122 MyListener(MetaDataUpdate.Server metaDataUpdateFactory) {
1123 this.metaDataUpdateFactory = metaDataUpdateFactory;
1124 }
1125
1126 @Override
1127 public void onGitReferenceUpdated(Event event) {
Edwin Kempina951ba52014-01-03 14:07:28 +01001128 if (event.getRefName().equals(RefNames.REFS_CONFIG)) {
Edwin Kempina46b6c92013-12-04 21:05:24 +01001129 Project.NameKey p = new Project.NameKey(event.getProjectName());
1130 try {
Edwin Kempina951ba52014-01-03 14:07:28 +01001131 ProjectConfig oldCfg = parseConfig(p, event.getOldObjectId());
1132 ProjectConfig newCfg = parseConfig(p, event.getNewObjectId());
Edwin Kempina46b6c92013-12-04 21:05:24 +01001133
Edwin Kempina951ba52014-01-03 14:07:28 +01001134 if (oldCfg != null && newCfg != null
1135 && !oldCfg.getProject().getSubmitType().equals(newCfg.getProject().getSubmitType())) {
Edwin Kempina46b6c92013-12-04 21:05:24 +01001136 // submit type has changed
1137 ...
1138 }
1139 } catch (IOException | ConfigInvalidException e) {
1140 ...
1141 }
1142 }
1143 }
Edwin Kempina951ba52014-01-03 14:07:28 +01001144
1145 private ProjectConfig parseConfig(Project.NameKey p, String idStr)
1146 throws IOException, ConfigInvalidException, RepositoryNotFoundException {
1147 ObjectId id = ObjectId.fromString(idStr);
1148 if (ObjectId.zeroId().equals(id)) {
1149 return null;
1150 }
1151 return ProjectConfig.read(metaDataUpdateFactory.create(p), id);
1152 }
Edwin Kempina46b6c92013-12-04 21:05:24 +01001153}
1154----
1155
1156
David Ostrovsky7066cc02013-06-15 14:46:23 +02001157[[capabilities]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001158== Plugin Owned Capabilities
David Ostrovsky7066cc02013-06-15 14:46:23 +02001159
1160Plugins may provide their own capabilities and restrict usage of SSH
Dariusz Luksza112d93a2014-06-01 16:52:23 +02001161commands or `UiAction` to the users who are granted those capabilities.
David Ostrovsky7066cc02013-06-15 14:46:23 +02001162
1163Plugins define the capabilities by overriding the `CapabilityDefinition`
1164abstract class:
1165
David Pursehouse68153d72013-09-04 10:09:17 +09001166[source,java]
1167----
1168public class PrintHelloCapability extends CapabilityDefinition {
1169 @Override
1170 public String getDescription() {
1171 return "Print Hello";
David Ostrovsky7066cc02013-06-15 14:46:23 +02001172 }
David Pursehouse68153d72013-09-04 10:09:17 +09001173}
1174----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001175
Dariusz Luksza112d93a2014-06-01 16:52:23 +02001176If no Guice modules are declared in the manifest, capability may
David Ostrovsky7066cc02013-06-15 14:46:23 +02001177use auto-registration by providing an `@Export` annotation:
1178
David Pursehouse68153d72013-09-04 10:09:17 +09001179[source,java]
1180----
1181@Export("printHello")
1182public class PrintHelloCapability extends CapabilityDefinition {
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
1187Otherwise the capability must be bound in a plugin module:
1188
David Pursehouse68153d72013-09-04 10:09:17 +09001189[source,java]
1190----
1191public class HelloWorldModule extends AbstractModule {
1192 @Override
1193 protected void configure() {
1194 bind(CapabilityDefinition.class)
1195 .annotatedWith(Exports.named("printHello"))
1196 .to(PrintHelloCapability.class);
David Ostrovsky7066cc02013-06-15 14:46:23 +02001197 }
David Pursehouse68153d72013-09-04 10:09:17 +09001198}
1199----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001200
1201With a plugin-owned capability defined in this way, it is possible to restrict
David Ostrovskyf86bae52013-09-01 09:10:39 +02001202usage of an SSH command or `UiAction` to members of the group that were granted
David Ostrovsky7066cc02013-06-15 14:46:23 +02001203this capability in the usual way, using the `RequiresCapability` annotation:
1204
David Pursehouse68153d72013-09-04 10:09:17 +09001205[source,java]
1206----
1207@RequiresCapability("printHello")
1208@CommandMetaData(name="print", description="Print greeting in different languages")
1209public final class PrintHelloWorldCommand extends SshCommand {
David Pursehoused128c892013-10-22 21:52:21 +09001210 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001211}
1212----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001213
David Ostrovskyf86bae52013-09-01 09:10:39 +02001214Or with `UiAction`:
David Ostrovsky7066cc02013-06-15 14:46:23 +02001215
David Pursehouse68153d72013-09-04 10:09:17 +09001216[source,java]
1217----
1218@RequiresCapability("printHello")
1219public class SayHelloAction extends UiAction<RevisionResource>
1220 implements RestModifyView<RevisionResource, SayHelloAction.Input> {
David Pursehoused128c892013-10-22 21:52:21 +09001221 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001222}
1223----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001224
1225Capability scope was introduced to differentiate between plugin-owned
David Pursehousebf053342013-09-05 14:55:29 +09001226capabilities and core capabilities. Per default the scope of the
1227`@RequiresCapability` annotation is `CapabilityScope.CONTEXT`, that means:
1228
David Ostrovsky7066cc02013-06-15 14:46:23 +02001229* when `@RequiresCapability` is used within a plugin the scope of the
1230capability is assumed to be that plugin.
David Pursehousebf053342013-09-05 14:55:29 +09001231
David Ostrovsky7066cc02013-06-15 14:46:23 +02001232* If `@RequiresCapability` is used within the core Gerrit Code Review server
1233(and thus is outside of a plugin) the scope is the core server and will use
1234the `GlobalCapability` known to Gerrit Code Review server.
1235
1236If a plugin needs to use a core capability name (e.g. "administrateServer")
1237this can be specified by setting `scope = CapabilityScope.CORE`:
1238
David Pursehouse68153d72013-09-04 10:09:17 +09001239[source,java]
1240----
1241@RequiresCapability(value = "administrateServer", scope =
1242 CapabilityScope.CORE)
David Pursehoused128c892013-10-22 21:52:21 +09001243 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001244----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001245
David Ostrovskyf86bae52013-09-01 09:10:39 +02001246[[ui_extension]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001247== UI Extension
David Ostrovskyf86bae52013-09-01 09:10:39 +02001248
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001249[[panels]]
1250=== Panels
1251
1252GWT plugins can contribute panels to Gerrit screens.
1253
1254Gerrit screens define extension points where plugins can add GWT
1255panels with custom controls:
1256
1257* Change Screen:
Edwin Kempin2a8c5152015-07-08 14:28:57 +02001258** `GerritUiExtensionPoint.CHANGE_SCREEN_HEADER`:
1259+
1260Panel will be shown in the header bar to the right of the change
1261status.
1262
Edwin Kempin745021e2015-07-09 13:09:44 +02001263** `GerritUiExtensionPoint.CHANGE_SCREEN_HEADER_RIGHT_OF_BUTTONS`:
1264+
1265Panel will be shown in the header bar on the right side of the buttons.
1266
Edwin Kempincbc95252015-07-09 11:37:53 +02001267** `GerritUiExtensionPoint.CHANGE_SCREEN_HEADER_RIGHT_OF_POP_DOWNS`:
1268+
1269Panel will be shown in the header bar on the right side of the pop down
1270buttons.
1271
Khai Do675afc02016-07-28 16:30:37 -07001272** `GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_COMMIT_INFO_BLOCK`:
1273+
1274Panel will be shown below the commit info block.
1275
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001276** `GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK`:
1277+
1278Panel will be shown below the change info block.
1279
Khai Do76c830c2016-07-28 16:35:45 -07001280** `GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_RELATED_INFO_BLOCK`:
1281+
1282Panel will be shown below the related info block.
1283
Khai Do83940ba2016-09-20 15:15:45 +02001284** `GerritUiExtensionPoint.CHANGE_SCREEN_HISTORY_RIGHT_OF_BUTTONS`:
1285+
1286Panel will be shown in the history bar on the right side of the buttons.
1287
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001288** The following parameters are provided:
Edwin Kempin5d683cc2015-07-10 15:47:14 +02001289*** `GerritUiExtensionPoint.Key.CHANGE_INFO`:
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001290+
Edwin Kempin5d683cc2015-07-10 15:47:14 +02001291The link:rest-api-changes.html#change-info[ChangeInfo] entity for the
1292current change.
David Ostrovsky916ae0c2016-03-15 17:05:41 +01001293+
1294The link:rest-api-changes.html#revision-info[RevisionInfo] entity for
1295the current patch set.
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001296
Edwin Kempin88b947a2015-07-08 09:03:56 +02001297* Project Info Screen:
1298** `GerritUiExtensionPoint.PROJECT_INFO_SCREEN_TOP`:
1299+
1300Panel will be shown at the top of the screen.
1301
1302** `GerritUiExtensionPoint.PROJECT_INFO_SCREEN_BOTTOM`:
1303+
1304Panel will be shown at the bottom of the screen.
1305
1306** The following parameters are provided:
1307*** `GerritUiExtensionPoint.Key.PROJECT_NAME`:
1308+
1309The name of the project.
1310
Edwin Kempin241d9db2015-07-08 13:53:50 +02001311* User Password Screen:
1312** `GerritUiExtensionPoint.PASSWORD_SCREEN_BOTTOM`:
1313+
1314Panel will be shown at the bottom of the screen.
1315
Edwin Kempin30c6f472015-07-09 14:27:52 +02001316** The following parameters are provided:
1317*** `GerritUiExtensionPoint.Key.ACCOUNT_INFO`:
1318+
1319The link:rest-api-accounts.html#account-info[AccountInfo] entity for
1320the current user.
1321
Edwin Kempin1cd95f92015-07-14 08:27:20 +02001322* User Preferences Screen:
1323** `GerritUiExtensionPoint.PREFERENCES_SCREEN_BOTTOM`:
1324+
1325Panel will be shown at the bottom of the screen.
1326
1327** The following parameters are provided:
1328*** `GerritUiExtensionPoint.Key.ACCOUNT_INFO`:
1329+
1330The link:rest-api-accounts.html#account-info[AccountInfo] entity for
1331the current user.
1332
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001333* User Profile Screen:
1334** `GerritUiExtensionPoint.PROFILE_SCREEN_BOTTOM`:
1335+
1336Panel will be shown at the bottom of the screen below the grid with the
1337profile data.
1338
Edwin Kempin30c6f472015-07-09 14:27:52 +02001339** The following parameters are provided:
1340*** `GerritUiExtensionPoint.Key.ACCOUNT_INFO`:
1341+
1342The link:rest-api-accounts.html#account-info[AccountInfo] entity for
1343the current user.
1344
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001345Example panel:
1346[source,java]
1347----
1348public class MyPlugin extends PluginEntryPoint {
1349 @Override
1350 public void onPluginLoad() {
1351 Plugin.get().panel(GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK,
Zac Livingstone7f3d1a2017-03-01 12:47:37 -07001352 "my_panel_name",
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001353 new Panel.EntryPoint() {
1354 @Override
1355 public void onLoad(Panel panel) {
1356 panel.setWidget(new InlineLabel("My Panel for change "
1357 + panel.getInt(GerritUiExtensionPoint.Key.CHANGE_ID, -1));
1358 }
1359 });
1360 }
1361}
1362----
1363
Zac Livingstone7f3d1a2017-03-01 12:47:37 -07001364Change Screen panel ordering may be specified in the
1365project config. Values may be either "plugin name" or
1366"plugin name"."panel name".
1367Panels not specified in the config will be added
1368to the end in load order. Panels specified in the config that
1369are not found will be ignored.
1370
1371Example config:
1372----
1373[extension-panels "CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK"]
1374 panel = helloworld.change_id
1375 panel = myotherplugin
1376 panel = myplugin.my_panel_name
1377----
1378
1379
1380
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001381[[actions]]
1382=== Actions
1383
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001384Plugins can contribute UI actions on core Gerrit pages. This is useful
1385for workflow customization or exposing plugin functionality through the
1386UI in addition to SSH commands and the REST API.
David Ostrovskyf86bae52013-09-01 09:10:39 +02001387
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001388For instance a plugin to integrate Jira with Gerrit changes may
1389contribute a "File bug" button to allow filing a bug from the change
1390page or plugins to integrate continuous integration systems may
1391contribute a "Schedule" button to allow a CI build to be scheduled
1392manually from the patch set panel.
David Ostrovskyf86bae52013-09-01 09:10:39 +02001393
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001394Two different places on core Gerrit pages are supported:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001395
1396* Change screen
1397* Project info screen
1398
1399Plugins contribute UI actions by implementing the `UiAction` interface:
1400
David Pursehouse68153d72013-09-04 10:09:17 +09001401[source,java]
1402----
1403@RequiresCapability("printHello")
1404class HelloWorldAction implements UiAction<RevisionResource>,
1405 RestModifyView<RevisionResource, HelloWorldAction.Input> {
1406 static class Input {
1407 boolean french;
1408 String message;
David Ostrovskyf86bae52013-09-01 09:10:39 +02001409 }
David Pursehouse68153d72013-09-04 10:09:17 +09001410
1411 private Provider<CurrentUser> user;
1412
1413 @Inject
1414 HelloWorldAction(Provider<CurrentUser> user) {
1415 this.user = user;
1416 }
1417
1418 @Override
1419 public String apply(RevisionResource rev, Input input) {
1420 final String greeting = input.french
1421 ? "Bonjour"
1422 : "Hello";
1423 return String.format("%s %s from change %s, patch set %d!",
1424 greeting,
1425 Strings.isNullOrEmpty(input.message)
1426 ? Objects.firstNonNull(user.get().getUserName(), "world")
1427 : input.message,
1428 rev.getChange().getId().toString(),
1429 rev.getPatchSet().getPatchSetId());
1430 }
1431
1432 @Override
1433 public Description getDescription(
1434 RevisionResource resource) {
1435 return new Description()
1436 .setLabel("Say hello")
1437 .setTitle("Say hello in different languages");
1438 }
1439}
1440----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001441
David Ostrovsky450eefe2013-10-21 21:18:11 +02001442Sometimes plugins may want to be able to change the state of a patch set or
1443change in the `UiAction.apply()` method and reflect these changes on the core
1444UI. For example a buildbot plugin which exposes a 'Schedule' button on the
1445patch set panel may want to disable that button after the build was scheduled
1446and update the tooltip of that button. But because of Gerrit's caching
1447strategy the following must be taken into consideration.
1448
1449The browser is allowed to cache the `UiAction` information until something on
1450the change is modified. More accurately the change row needs to be modified in
1451the database to have a more recent `lastUpdatedOn` or a new `rowVersion`, or
1452the +refs/meta/config+ of the project or any parents needs to change to a new
1453SHA-1. The ETag SHA-1 computation code can be found in the
1454`ChangeResource.getETag()` method.
1455
David Pursehoused128c892013-10-22 21:52:21 +09001456The easiest way to accomplish this is to update `lastUpdatedOn` of the change:
David Ostrovsky450eefe2013-10-21 21:18:11 +02001457
1458[source,java]
1459----
1460@Override
1461public Object apply(RevisionResource rcrs, Input in) {
1462 // schedule a build
1463 [...]
1464 // update change
1465 ReviewDb db = dbProvider.get();
Edwin Kempine2d06b02016-02-17 18:34:17 +01001466 try (BatchUpdate bu = batchUpdateFactory.create(
1467 db, project.getNameKey(), user, TimeUtil.nowTs())) {
1468 bu.addOp(change.getId(), new BatchUpdate.Op() {
1469 @Override
Dave Borowitzb91cf222017-03-10 13:11:59 -05001470 public boolean updateChange(ChangeContext ctx) {
Edwin Kempine2d06b02016-02-17 18:34:17 +01001471 return true;
1472 }
1473 });
1474 bu.execute();
David Ostrovsky450eefe2013-10-21 21:18:11 +02001475 }
David Pursehoused128c892013-10-22 21:52:21 +09001476 [...]
David Ostrovsky450eefe2013-10-21 21:18:11 +02001477}
1478----
1479
David Ostrovskyf86bae52013-09-01 09:10:39 +02001480`UiAction` must be bound in a plugin module:
1481
David Pursehouse68153d72013-09-04 10:09:17 +09001482[source,java]
1483----
1484public class Module extends AbstractModule {
1485 @Override
1486 protected void configure() {
1487 install(new RestApiModule() {
1488 @Override
1489 protected void configure() {
1490 post(REVISION_KIND, "say-hello")
1491 .to(HelloWorldAction.class);
1492 }
1493 });
David Ostrovskyf86bae52013-09-01 09:10:39 +02001494 }
David Pursehouse68153d72013-09-04 10:09:17 +09001495}
1496----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001497
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001498The module above must be declared in the `pom.xml` for Maven driven
1499plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001500
David Pursehouse68153d72013-09-04 10:09:17 +09001501[source,xml]
1502----
1503<manifestEntries>
1504 <Gerrit-Module>com.googlesource.gerrit.plugins.cookbook.Module</Gerrit-Module>
1505</manifestEntries>
1506----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001507
David Ostrovskyfdbfcad2016-11-15 06:35:29 -08001508or in the `BUILD` configuration file for Bazel driven plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001509
David Pursehouse68153d72013-09-04 10:09:17 +09001510[source,python]
1511----
1512manifest_entries = [
1513 'Gerrit-Module: com.googlesource.gerrit.plugins.cookbook.Module',
1514]
1515----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001516
1517In some use cases more user input must be gathered, for that `UiAction` can be
1518combined with the JavaScript API. This would display a small popup near the
1519activation button to gather additional input from the user. The JS file is
1520typically put in the `static` folder within the plugin's directory:
1521
David Pursehouse68153d72013-09-04 10:09:17 +09001522[source,javascript]
1523----
1524Gerrit.install(function(self) {
1525 function onSayHello(c) {
1526 var f = c.textfield();
1527 var t = c.checkbox();
1528 var b = c.button('Say hello', {onclick: function(){
1529 c.call(
1530 {message: f.value, french: t.checked},
1531 function(r) {
1532 c.hide();
1533 window.alert(r);
1534 c.refresh();
1535 });
1536 }});
1537 c.popup(c.div(
1538 c.prependLabel('Greeting message', f),
1539 c.br(),
1540 c.label(t, 'french'),
1541 c.br(),
1542 b));
1543 f.focus();
1544 }
1545 self.onAction('revision', 'say-hello', onSayHello);
1546});
1547----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001548
1549The JS module must be exposed as a `WebUiPlugin` and bound as
1550an HTTP Module:
1551
David Pursehouse68153d72013-09-04 10:09:17 +09001552[source,java]
1553----
1554public class HttpModule extends HttpPluginModule {
1555 @Override
1556 protected void configureServlets() {
1557 DynamicSet.bind(binder(), WebUiPlugin.class)
1558 .toInstance(new JavaScriptPlugin("hello.js"));
David Ostrovskyf86bae52013-09-01 09:10:39 +02001559 }
David Pursehouse68153d72013-09-04 10:09:17 +09001560}
1561----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001562
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001563The HTTP module above must be declared in the `pom.xml` for Maven
1564driven plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001565
David Pursehouse68153d72013-09-04 10:09:17 +09001566[source,xml]
1567----
1568<manifestEntries>
1569 <Gerrit-HttpModule>com.googlesource.gerrit.plugins.cookbook.HttpModule</Gerrit-HttpModule>
1570</manifestEntries>
1571----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001572
David Ostrovskyfdbfcad2016-11-15 06:35:29 -08001573or in the `BUILD` configuration file for Bazel driven plugins
David Ostrovskyf86bae52013-09-01 09:10:39 +02001574
David Pursehouse68153d72013-09-04 10:09:17 +09001575[source,python]
1576----
1577manifest_entries = [
1578 'Gerrit-HttpModule: com.googlesource.gerrit.plugins.cookbook.HttpModule',
1579]
1580----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001581
1582If `UiAction` is annotated with the `@RequiresCapability` annotation, then the
1583capability check is done during the `UiAction` gathering, so the plugin author
1584doesn't have to set `UiAction.Description.setVisible()` explicitly in this
1585case.
1586
David Pursehousea61ee502016-09-06 16:27:09 +09001587The following prerequisites must be met, to satisfy the capability check:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001588
1589* user is authenticated
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001590* user is a member of a group which has the `Administrate Server` capability, or
David Ostrovskyf86bae52013-09-01 09:10:39 +02001591* user is a member of a group which has the required capability
1592
1593The `apply` method is called when the button is clicked. If `UiAction` is
1594combined with JavaScript API (its own JavaScript function is provided),
1595then a popup dialog is normally opened to gather additional user input.
1596A new button is placed on the popup dialog to actually send the request.
1597
1598Every `UiAction` exposes a REST API endpoint. The endpoint from the example above
1599can be accessed from any REST client, i. e.:
1600
Michael Ochmannb99feab2016-07-06 14:10:22 +02001601----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001602 curl -X POST -H "Content-Type: application/json" \
1603 -d '{message: "François", french: true}' \
Han-Wen Nienhuys84d830b2017-02-15 16:36:04 +01001604 --user joe:secret \
David Ostrovskyf86bae52013-09-01 09:10:39 +02001605 http://host:port/a/changes/1/revisions/1/cookbook~say-hello
1606 "Bonjour François from change 1, patch set 1!"
Michael Ochmannb99feab2016-07-06 14:10:22 +02001607----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001608
David Pursehouse42245822013-09-24 09:48:20 +09001609A special case is to bind an endpoint without a view name. This is
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001610particularly useful for `DELETE` requests:
David Ostrovskyc6d19ed2013-09-20 21:30:18 +02001611
1612[source,java]
1613----
1614public class Module extends AbstractModule {
1615 @Override
1616 protected void configure() {
1617 install(new RestApiModule() {
1618 @Override
1619 protected void configure() {
1620 delete(PROJECT_KIND)
1621 .to(DeleteProject.class);
1622 }
1623 });
1624 }
1625}
1626----
1627
David Pursehouse42245822013-09-24 09:48:20 +09001628For a `UiAction` bound this way, a JS API function can be provided.
1629
1630Currently only one restriction exists: per plugin only one `UiAction`
David Ostrovskyc6d19ed2013-09-20 21:30:18 +02001631can be bound per resource without view name. To define a JS function
1632for the `UiAction`, "/" must be used as the name:
1633
1634[source,javascript]
1635----
1636Gerrit.install(function(self) {
1637 function onDeleteProject(c) {
1638 [...]
1639 }
1640 self.onAction('project', '/', onDeleteProject);
1641});
1642----
1643
Dave Borowitzf1790ce2016-11-14 12:26:12 -08001644
1645[[action-visitor]]
1646=== Action Visitors
1647
1648In addition to providing new actions, plugins can have fine-grained control
1649over the link:rest-api-changes.html#action-info[ActionInfo] map, modifying or
1650removing existing actions, including those contributed by core.
1651
1652Visitors are provided the link:rest-api-changes.html#action-info[ActionInfo],
1653which is mutable, along with copies of the
1654link:rest-api-changes.html#change-info[ChangeInfo] and
1655link:rest-api-changes.html#revision-info[RevisionInfo]. They can modify the
1656action, or return `false` to exclude it from the resulting map.
1657
1658These operations only affect the action buttons that are displayed in the UI;
1659the underlying REST API endpoints are not affected. Multiple plugins may
1660implement the visitor interface, but the order in which they are run is
1661undefined.
1662
1663For example, to exclude "Cherry-Pick" only from certain projects, and rename
1664"Abandon":
1665
1666[source,java]
1667----
1668public class MyActionVisitor implements ActionVisitor {
1669 @Override
1670 public boolean visit(String name, ActionInfo actionInfo,
1671 ChangeInfo changeInfo) {
1672 if (name.equals("abandon")) {
1673 actionInfo.label = "Drop";
1674 }
1675 return true;
1676 }
1677
1678 @Override
1679 public boolean visit(String name, ActionInfo actionInfo,
1680 ChangeInfo changeInfo, RevisionInfo revisionInfo) {
1681 if (project.startsWith("some-team/") && name.equals("cherrypick")) {
1682 return false;
1683 }
1684 return true;
1685 }
1686}
1687----
1688
1689
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001690[[top-menu-extensions]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001691== Top Menu Extensions
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001692
1693Plugins can contribute items to Gerrit's top menu.
1694
1695A single top menu extension can have multiple elements and will be put as
1696the last element in Gerrit's top menu.
1697
1698Plugins define the top menu entries by implementing `TopMenu` interface:
1699
1700[source,java]
1701----
1702public class MyTopMenuExtension implements TopMenu {
1703
1704 @Override
1705 public List<MenuEntry> getEntries() {
1706 return Lists.newArrayList(
1707 new MenuEntry("Top Menu Entry", Lists.newArrayList(
1708 new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
1709 }
1710}
1711----
1712
Edwin Kempin77f23242013-09-30 14:53:20 +02001713Plugins can also add additional menu items to Gerrit's top menu entries
1714by defining a `MenuEntry` that has the same name as a Gerrit top menu
1715entry:
1716
1717[source,java]
1718----
1719public class MyTopMenuExtension implements TopMenu {
1720
1721 @Override
1722 public List<MenuEntry> getEntries() {
1723 return Lists.newArrayList(
Dariusz Luksza2d3afab2013-10-01 11:07:13 +02001724 new MenuEntry(GerritTopMenu.PROJECTS, Lists.newArrayList(
Edwin Kempin77f23242013-09-30 14:53:20 +02001725 new MenuItem("Browse Repositories", "https://gerrit.googlesource.com/"))));
1726 }
1727}
1728----
1729
Dariusz Lukszae8de74f2014-06-04 19:39:20 +02001730`MenuItems` that are bound for the `MenuEntry` with the name
1731`GerritTopMenu.PROJECTS` can contain a `${projectName}` placeholder
1732which is automatically replaced by the actual project name.
1733
1734E.g. plugins may register an link:#http[HTTP Servlet] to handle project
1735specific requests and add an menu item for this:
1736
1737[source,java]
1738---
1739 new MenuItem("My Screen", "/plugins/myplugin/project/${projectName}");
1740---
1741
1742This also enables plugins to provide menu items for project aware
1743screens:
1744
1745[source,java]
1746---
1747 new MenuItem("My Screen", "/x/my-screen/for/${projectName}");
1748---
1749
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001750If no Guice modules are declared in the manifest, the top menu extension may use
1751auto-registration by providing an `@Listen` annotation:
1752
1753[source,java]
1754----
1755@Listen
1756public class MyTopMenuExtension implements TopMenu {
David Pursehoused128c892013-10-22 21:52:21 +09001757 [...]
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001758}
1759----
1760
Luca Milanesiocb230402013-10-11 08:49:56 +01001761Otherwise the top menu extension must be bound in the plugin module used
1762for the Gerrit system injector (Gerrit-Module entry in MANIFEST.MF):
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001763
1764[source,java]
1765----
Luca Milanesiocb230402013-10-11 08:49:56 +01001766package com.googlesource.gerrit.plugins.helloworld;
1767
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001768public class HelloWorldModule extends AbstractModule {
1769 @Override
1770 protected void configure() {
1771 DynamicSet.bind(binder(), TopMenu.class).to(MyTopMenuExtension.class);
1772 }
1773}
1774----
1775
Luca Milanesiocb230402013-10-11 08:49:56 +01001776[source,manifest]
1777----
1778Gerrit-ApiType: plugin
1779Gerrit-Module: com.googlesource.gerrit.plugins.helloworld.HelloWorldModule
1780----
1781
Edwin Kempinb2e926a2013-11-11 16:38:30 +01001782It is also possible to show some menu entries only if the user has a
1783certain capability:
1784
1785[source,java]
1786----
1787public class MyTopMenuExtension implements TopMenu {
1788 private final String pluginName;
1789 private final Provider<CurrentUser> userProvider;
1790 private final List<MenuEntry> menuEntries;
1791
1792 @Inject
1793 public MyTopMenuExtension(@PluginName String pluginName,
1794 Provider<CurrentUser> userProvider) {
1795 this.pluginName = pluginName;
1796 this.userProvider = userProvider;
1797 menuEntries = new ArrayList<TopMenu.MenuEntry>();
1798
1799 // add menu entry that is only visible to users with a certain capability
1800 if (canSeeMenuEntry()) {
1801 menuEntries.add(new MenuEntry("Top Menu Entry", Collections
1802 .singletonList(new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
1803 }
1804
1805 // add menu entry that is visible to all users (even anonymous users)
1806 menuEntries.add(new MenuEntry("Top Menu Entry", Collections
1807 .singletonList(new MenuItem("Documentation", "/plugins/myplugin/"))));
1808 }
1809
1810 private boolean canSeeMenuEntry() {
1811 if (userProvider.get().isIdentifiedUser()) {
1812 CapabilityControl ctl = userProvider.get().getCapabilities();
1813 return ctl.canPerform(pluginName + "-" + MyCapability.ID)
1814 || ctl.canAdministrateServer();
1815 } else {
1816 return false;
1817 }
1818 }
1819
1820 @Override
1821 public List<MenuEntry> getEntries() {
1822 return menuEntries;
1823 }
1824}
1825----
1826
Dave Borowitzf1790ce2016-11-14 12:26:12 -08001827
Edwin Kempin3c024ea2013-11-11 10:43:46 +01001828[[gwt_ui_extension]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001829== GWT UI Extension
Edwin Kempin3c024ea2013-11-11 10:43:46 +01001830Plugins can extend the Gerrit UI with own GWT code.
1831
Edwin Kempinb74daa92013-11-11 11:28:16 +01001832A GWT plugin must contain a GWT module file, e.g. `HelloPlugin.gwt.xml`,
1833that bundles together all the configuration settings of the GWT plugin:
1834
1835[source,xml]
1836----
1837<?xml version="1.0" encoding="UTF-8"?>
1838<module rename-to="hello_gwt_plugin">
1839 <!-- Inherit the core Web Toolkit stuff. -->
1840 <inherits name="com.google.gwt.user.User"/>
1841 <!-- Other module inherits -->
1842 <inherits name="com.google.gerrit.Plugin"/>
1843 <inherits name="com.google.gwt.http.HTTP"/>
1844 <!-- Using GWT built-in themes adds a number of static -->
1845 <!-- resources to the plugin. No theme inherits lines were -->
1846 <!-- added in order to make this plugin as simple as possible -->
1847 <!-- Specify the app entry point class. -->
1848 <entry-point class="${package}.client.HelloPlugin"/>
1849 <stylesheet src="hello.css"/>
1850</module>
1851----
1852
1853The GWT module must inherit `com.google.gerrit.Plugin` and
1854`com.google.gwt.http.HTTP`.
1855
1856To register the GWT module a `GwtPlugin` needs to be bound.
1857
1858If no Guice modules are declared in the manifest, the GWT plugin may
1859use auto-registration by using the `@Listen` annotation:
1860
1861[source,java]
1862----
1863@Listen
1864public class MyExtension extends GwtPlugin {
1865 public MyExtension() {
1866 super("hello_gwt_plugin");
1867 }
1868}
1869----
1870
1871Otherwise the binding must be done in an `HttpModule`:
1872
1873[source,java]
1874----
1875public class HttpModule extends HttpPluginModule {
1876
1877 @Override
1878 protected void configureServlets() {
1879 DynamicSet.bind(binder(), WebUiPlugin.class)
1880 .toInstance(new GwtPlugin("hello_gwt_plugin"));
1881 }
1882}
1883----
1884
1885The HTTP module above must be declared in the `pom.xml` for Maven
1886driven plugins:
1887
1888[source,xml]
1889----
1890<manifestEntries>
1891 <Gerrit-HttpModule>com.googlesource.gerrit.plugins.myplugin.HttpModule</Gerrit-HttpModule>
1892</manifestEntries>
1893----
1894
Shawn Pearcec8e96ad2013-12-09 08:20:44 -08001895The name that is provided to the `GwtPlugin` must match the GWT
1896module name compiled into the plugin. The name of the GWT module
1897can be explicitly set in the GWT module XML file by specifying
1898the `rename-to` attribute on the module. It is important that the
1899module name be unique across all plugins installed on the server,
1900as the module name determines the JavaScript namespace used by the
1901compiled plugin code.
Edwin Kempinb74daa92013-11-11 11:28:16 +01001902
1903[source,xml]
1904----
1905<module rename-to="hello_gwt_plugin">
1906----
1907
1908The actual GWT code must be implemented in a class that extends
Shawn Pearcec8e96ad2013-12-09 08:20:44 -08001909`com.google.gerrit.plugin.client.PluginEntryPoint`:
Edwin Kempinb74daa92013-11-11 11:28:16 +01001910
1911[source,java]
1912----
Shawn Pearcec8e96ad2013-12-09 08:20:44 -08001913public class HelloPlugin extends PluginEntryPoint {
Edwin Kempinb74daa92013-11-11 11:28:16 +01001914
1915 @Override
Shawn Pearcec8e96ad2013-12-09 08:20:44 -08001916 public void onPluginLoad() {
Edwin Kempinb74daa92013-11-11 11:28:16 +01001917 // Create the dialog box
1918 final DialogBox dialogBox = new DialogBox();
1919
1920 // The content of the dialog comes from a User specified Preference
1921 dialogBox.setText("Hello from GWT Gerrit UI plugin");
1922 dialogBox.setAnimationEnabled(true);
1923 Button closeButton = new Button("Close");
1924 VerticalPanel dialogVPanel = new VerticalPanel();
1925 dialogVPanel.setWidth("100%");
1926 dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
1927 dialogVPanel.add(closeButton);
1928
1929 closeButton.addClickHandler(new ClickHandler() {
1930 public void onClick(ClickEvent event) {
1931 dialogBox.hide();
1932 }
1933 });
1934
1935 // Set the contents of the Widget
1936 dialogBox.setWidget(dialogVPanel);
1937
1938 RootPanel rootPanel = RootPanel.get(HelloMenu.MENU_ID);
1939 rootPanel.getElement().removeAttribute("href");
1940 rootPanel.addDomHandler(new ClickHandler() {
1941 @Override
1942 public void onClick(ClickEvent event) {
1943 dialogBox.center();
1944 dialogBox.show();
1945 }
1946 }, ClickEvent.getType());
1947 }
1948}
1949----
1950
1951This class must be set as entry point in the GWT module:
1952
1953[source,xml]
1954----
1955<entry-point class="${package}.client.HelloPlugin"/>
1956----
1957
1958In addition this class must be defined as module in the `pom.xml` for the
1959`gwt-maven-plugin` and the `webappDirectory` option of `gwt-maven-plugin`
1960must be set to `${project.build.directory}/classes/static`:
1961
1962[source,xml]
1963----
1964<plugin>
1965 <groupId>org.codehaus.mojo</groupId>
1966 <artifactId>gwt-maven-plugin</artifactId>
David Pursehouse7ab81732015-05-07 12:00:47 +09001967 <version>2.7.0</version>
Edwin Kempinb74daa92013-11-11 11:28:16 +01001968 <configuration>
1969 <module>com.googlesource.gerrit.plugins.myplugin.HelloPlugin</module>
1970 <disableClassMetadata>true</disableClassMetadata>
1971 <disableCastChecking>true</disableCastChecking>
1972 <webappDirectory>${project.build.directory}/classes/static</webappDirectory>
1973 </configuration>
1974 <executions>
1975 <execution>
1976 <goals>
1977 <goal>compile</goal>
1978 </goals>
1979 </execution>
1980 </executions>
1981</plugin>
1982----
1983
1984To attach a GWT widget defined by the plugin to the Gerrit core UI
1985`com.google.gwt.user.client.ui.RootPanel` can be used to manipulate the
1986Gerrit core widgets:
1987
1988[source,java]
1989----
1990RootPanel rootPanel = RootPanel.get(HelloMenu.MENU_ID);
1991rootPanel.getElement().removeAttribute("href");
1992rootPanel.addDomHandler(new ClickHandler() {
1993 @Override
1994 public void onClick(ClickEvent event) {
1995 dialogBox.center();
1996 dialogBox.show();
1997 }
1998}, ClickEvent.getType());
1999----
2000
2001GWT plugins can come with their own css file. This css file must have a
2002unique name and must be registered in the GWT module:
2003
2004[source,xml]
2005----
2006<stylesheet src="hello.css"/>
2007----
2008
Edwin Kempin2570b102013-11-11 11:44:50 +01002009If a GWT plugin wants to invoke the Gerrit REST API it can use
David Pursehouse3a388312014-02-25 16:41:47 +09002010`com.google.gerrit.plugin.client.rpc.RestApi` to construct the URL
Edwin Kempin2570b102013-11-11 11:44:50 +01002011path and to trigger the REST calls.
2012
2013Example for invoking a Gerrit core REST endpoint:
2014
2015[source,java]
2016----
2017new RestApi("projects").id(projectName).view("description")
2018 .put("new description", new AsyncCallback<JavaScriptObject>() {
2019
2020 @Override
2021 public void onSuccess(JavaScriptObject result) {
2022 // TODO
2023 }
2024
2025 @Override
2026 public void onFailure(Throwable caught) {
2027 // never invoked
2028 }
2029});
2030----
2031
2032Example for invoking a REST endpoint defined by a plugin:
2033
2034[source,java]
2035----
2036new RestApi("projects").id(projectName).view("myplugin", "myview")
2037 .get(new AsyncCallback<JavaScriptObject>() {
2038
2039 @Override
2040 public void onSuccess(JavaScriptObject result) {
2041 // TODO
2042 }
2043
2044 @Override
2045 public void onFailure(Throwable caught) {
2046 // never invoked
2047 }
2048});
2049----
2050
2051The `onFailure(Throwable)` of the provided callback is never invoked.
2052If an error occurs, it is shown in an error dialog.
2053
2054In order to be able to do REST calls the GWT module must inherit
2055`com.google.gwt.json.JSON`:
2056
2057[source,xml]
2058----
2059<inherits name="com.google.gwt.json.JSON"/>
2060----
2061
Edwin Kempin15199792014-04-23 16:22:05 +02002062[[screen]]
Shawn Pearced5c844f2013-12-26 15:32:26 -08002063== Add Screen
Edwin Kempin15199792014-04-23 16:22:05 +02002064A link:#gwt_ui_extension[GWT plugin] can link:#top-menu-extensions[add
2065a menu item] that opens a screen that is implemented by the plugin.
2066This way plugin screens can be fully integrated into the Gerrit UI.
Shawn Pearced5c844f2013-12-26 15:32:26 -08002067
2068Example menu item:
2069[source,java]
2070----
2071public class MyMenu implements TopMenu {
2072 private final List<MenuEntry> menuEntries;
2073
2074 @Inject
2075 public MyMenu(@PluginName String name) {
David Pursehouseccdeae82016-05-03 23:16:15 +09002076 menuEntries = new ArrayList<>();
Shawn Pearced5c844f2013-12-26 15:32:26 -08002077 menuEntries.add(new MenuEntry("My Menu", Collections.singletonList(
2078 new MenuItem("My Screen", "#/x/" + name + "/my-screen", ""))));
2079 }
2080
2081 @Override
2082 public List<MenuEntry> getEntries() {
2083 return menuEntries;
2084 }
2085}
2086----
2087
2088Example screen:
2089[source,java]
2090----
2091public class MyPlugin extends PluginEntryPoint {
2092 @Override
2093 public void onPluginLoad() {
2094 Plugin.get().screen("my-screen", new Screen.EntryPoint() {
2095 @Override
2096 public void onLoad(Screen screen) {
2097 screen.add(new InlineLabel("My Screen");
2098 screen.show();
2099 }
2100 });
2101 }
2102}
2103----
2104
Edwin Kempin70e11122015-07-08 13:28:40 +02002105[[user-settings-screen]]
2106== Add User Settings Screen
2107
2108A link:#gwt_ui_extension[GWT plugin] can implement a user settings
2109screen that is integrated into the Gerrit user settings menu.
2110
2111Example settings screen:
2112[source,java]
2113----
2114public class MyPlugin extends PluginEntryPoint {
2115 @Override
2116 public void onPluginLoad() {
2117 Plugin.get().settingsScreen("my-preferences", "My Preferences",
2118 new Screen.EntryPoint() {
2119 @Override
2120 public void onLoad(Screen screen) {
2121 screen.setPageTitle("Settings");
2122 screen.add(new InlineLabel("My Preferences"));
2123 screen.show();
2124 }
2125 });
2126 }
2127}
2128----
2129
Edwin Kempinfa0d4942015-07-16 12:38:52 +02002130By defining an link:config-gerrit.html#urlAlias[urlAlias] Gerrit
2131administrators can map plugin screens into the Gerrit URL namespace or
2132even replace Gerrit screens by plugin screens.
2133
Edwin Kempinb1e6a3a2015-07-22 15:36:56 +02002134Plugins may also programatically add URL aliases in the preferences of
2135of a user. This way certain screens can be replaced for certain users.
2136E.g. the plugin may offer a user preferences setting for choosing a
2137screen that then sets/unsets a URL alias for the user.
2138
Edwin Kempin289f1a02014-02-04 16:08:25 +01002139[[settings-screen]]
2140== Plugin Settings Screen
2141
2142If a plugin implements a screen for administrating its settings that is
2143available under "#/x/<plugin-name>/settings" it is automatically linked
2144from the plugin list screen.
2145
Edwin Kempinf5a77332012-07-18 11:17:53 +02002146[[http]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002147== HTTP Servlets
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002148
2149Plugins or extensions may register additional HTTP servlets, and
2150wrap them with HTTP filters.
2151
2152Servlets may use auto-registration to declare the URL they handle:
2153
David Pursehouse68153d72013-09-04 10:09:17 +09002154[source,java]
2155----
2156import com.google.gerrit.extensions.annotations.Export;
2157import com.google.inject.Singleton;
2158import javax.servlet.http.HttpServlet;
2159import javax.servlet.http.HttpServletRequest;
2160import javax.servlet.http.HttpServletResponse;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002161
David Pursehouse68153d72013-09-04 10:09:17 +09002162@Export("/print")
2163@Singleton
2164class HelloServlet extends HttpServlet {
2165 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
2166 res.setContentType("text/plain");
2167 res.setCharacterEncoding("UTF-8");
2168 res.getWriter().write("Hello");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002169 }
David Pursehouse68153d72013-09-04 10:09:17 +09002170}
2171----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002172
Edwin Kempin8aa650f2012-07-18 11:25:48 +02002173The auto registration only works for standard servlet mappings like
Jonathan Nieder5758f182015-03-30 11:28:55 -07002174`/foo` or `+/foo/*+`. Regex style bindings must use a Guice ServletModule
Edwin Kempin8aa650f2012-07-18 11:25:48 +02002175to register the HTTP servlets and declare it explicitly in the manifest
2176with the `Gerrit-HttpModule` attribute:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002177
David Pursehouse68153d72013-09-04 10:09:17 +09002178[source,java]
2179----
2180import com.google.inject.servlet.ServletModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002181
David Pursehouse68153d72013-09-04 10:09:17 +09002182class MyWebUrls extends ServletModule {
2183 protected void configureServlets() {
2184 serve("/print").with(HelloServlet.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002185 }
David Pursehouse68153d72013-09-04 10:09:17 +09002186}
2187----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002188
2189For a plugin installed as name `helloworld`, the servlet implemented
2190by HelloServlet class will be available to users as:
2191
2192----
2193$ curl http://review.example.com/plugins/helloworld/print
2194----
Nasser Grainawie033b262012-05-09 17:54:21 -07002195
Edwin Kempinf5a77332012-07-18 11:17:53 +02002196[[data-directory]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002197== Data Directory
Edwin Kempin41f63912012-07-17 12:33:55 +02002198
Dave Borowitz9e158752015-02-24 10:17:04 -08002199Plugins can request a data directory with a `@PluginData` Path (or File,
2200deprecated) dependency. A data directory will be created automatically
2201by the server in `$site_path/data/$plugin_name` and passed to the
2202plugin.
Edwin Kempin41f63912012-07-17 12:33:55 +02002203
2204Plugins can use this to store any data they want.
2205
David Pursehouse68153d72013-09-04 10:09:17 +09002206[source,java]
2207----
2208@Inject
Dave Borowitz9e158752015-02-24 10:17:04 -08002209MyType(@PluginData java.nio.file.Path myDir) {
2210 this.in = Files.newInputStream(myDir.resolve("my.config"));
David Pursehouse68153d72013-09-04 10:09:17 +09002211}
2212----
Edwin Kempin41f63912012-07-17 12:33:55 +02002213
Dariusz Lukszaebab92a2014-09-10 11:14:19 +02002214[[secure-store]]
2215== SecureStore
2216
2217SecureStore allows to change the way Gerrit stores sensitive data like
2218passwords.
2219
2220In order to replace the default SecureStore (no-op) implementation,
2221a class that extends `com.google.gerrit.server.securestore.SecureStore`
2222needs to be provided (with dependencies) in a separate jar file. Then
2223link:pgm-SwitchSecureStore.html[SwitchSecureStore] must be run to
2224switch implementations.
2225
2226The SecureStore implementation is instantiated using a Guice injector
2227which binds the `File` annotated with the `@SitePath` annotation.
2228This means that a SecureStore implementation class can get access to
2229the `site_path` like in the following example:
2230
2231[source,java]
2232----
2233@Inject
2234MySecureStore(@SitePath java.io.File sitePath) {
2235 // your code
2236}
2237----
2238
2239No Guice bindings or modules are required. Gerrit will automatically
2240discover and bind the implementation.
2241
Michael Ochmann24612652016-02-12 17:26:18 +01002242[[accountcreation]]
2243== Account Creation
2244
2245Plugins can hook into the
2246link:rest-api-accounts.html#create-account[account creation] REST API and
2247inject additional external identifiers for an account that represents a user
2248in some external user store. For that, an implementation of the extension
2249point `com.google.gerrit.server.api.accounts.AccountExternalIdCreator`
2250must be registered.
2251
2252[source,java]
2253----
2254class MyExternalIdCreator implements AccountExternalIdCreator {
2255 @Override
2256 public List<AccountExternalId> create(Account.Id id, String username,
2257 String email) {
2258 // your code
2259 }
2260}
2261
2262bind(AccountExternalIdCreator.class)
2263 .annotatedWith(UniqueAnnotations.create())
2264 .to(MyExternalIdCreator.class);
2265}
2266----
2267
Edwin Kempinea621482013-10-16 12:58:24 +02002268[[download-commands]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002269== Download Commands
Edwin Kempinea621482013-10-16 12:58:24 +02002270
Edwin Kempineafde882015-05-11 15:40:44 +02002271Gerrit offers commands for downloading changes and cloning projects
2272using different download schemes (e.g. for downloading via different
2273network protocols). Plugins can contribute download schemes, download
2274commands and clone commands by implementing
2275`com.google.gerrit.extensions.config.DownloadScheme`,
2276`com.google.gerrit.extensions.config.DownloadCommand` and
2277`com.google.gerrit.extensions.config.CloneCommand`.
Edwin Kempinea621482013-10-16 12:58:24 +02002278
Edwin Kempineafde882015-05-11 15:40:44 +02002279The download schemes, download commands and clone commands which are
2280used most often are provided by the Gerrit core plugin
2281`download-commands`.
Edwin Kempinea621482013-10-16 12:58:24 +02002282
Edwin Kempin78279ba2015-05-22 15:22:41 +02002283[[included-in]]
2284== Included In
2285
2286For merged changes the link:user-review-ui.html#included-in[Included In]
2287drop-down panel shows the branches and tags in which the change is
2288included.
2289
2290Plugins can add additional systems in which the change can be included
2291by implementing `com.google.gerrit.extensions.config.ExternalIncludedIn`,
2292e.g. a plugin can provide a list of servers on which the change was
2293deployed.
2294
Sven Selbergae1a10c2014-02-14 14:24:29 +01002295[[links-to-external-tools]]
2296== Links To External Tools
2297
2298Gerrit has extension points that enables development of a
2299light-weight plugin that links commits to external
2300tools (GitBlit, CGit, company specific resources etc).
2301
2302PatchSetWebLinks will appear to the right of the commit-SHA1 in the UI.
2303
2304[source, java]
2305----
2306import com.google.gerrit.extensions.annotations.Listen;
2307import com.google.gerrit.extensions.webui.PatchSetWebLink;;
Sven Selberga85e64d2014-09-24 10:52:21 +02002308import com.google.gerrit.extensions.webui.WebLinkTarget;
Sven Selbergae1a10c2014-02-14 14:24:29 +01002309
2310@Listen
2311public class MyWeblinkPlugin implements PatchSetWebLink {
2312
2313 private String name = "MyLink";
2314 private String placeHolderUrlProjectCommit = "http://my.tool.com/project=%s/commit=%s";
Sven Selberg55484202014-06-26 08:48:51 +02002315 private String imageUrl = "http://placehold.it/16x16.gif";
Sven Selbergae1a10c2014-02-14 14:24:29 +01002316
2317 @Override
Jonathan Niederb3cd6902015-03-12 16:19:15 -07002318 public WebLinkInfo getPatchSetWebLink(String projectName, String commit) {
Sven Selberga85e64d2014-09-24 10:52:21 +02002319 return new WebLinkInfo(name,
2320 imageUrl,
2321 String.format(placeHolderUrlProjectCommit, project, commit),
2322 WebLinkTarget.BLANK);
Edwin Kempinceeed6b2014-09-11 17:07:33 +02002323 }
Sven Selbergae1a10c2014-02-14 14:24:29 +01002324}
2325----
2326
David Pursehouse58b8d762016-12-09 11:12:27 +09002327ParentWebLinks will appear to the right of the SHA1 of the parent
2328revisions in the UI. The implementation should in most use cases direct
2329to the same external service as PatchSetWebLink; it is provided as a
2330separate interface because not all users want to have links for the
2331parent revisions.
2332
Edwin Kempinb3696c82014-09-11 09:41:42 +02002333FileWebLinks will appear in the side-by-side diff screen on the right
2334side of the patch selection on each side.
2335
Edwin Kempin8cdce502014-12-06 10:55:38 +01002336DiffWebLinks will appear in the side-by-side and unified diff screen in
2337the header next to the navigation icons.
2338
Edwin Kempinea004752014-04-11 15:56:02 +02002339ProjectWebLinks will appear in the project list in the
2340`Repository Browser` column.
2341
Edwin Kempin0f697bd2014-09-10 18:23:29 +02002342BranchWebLinks will appear in the branch list in the last column.
2343
Edwin Kempinf82b8122016-06-03 09:20:16 +02002344FileHistoryWebLinks will appear on the access rights screen.
2345
Paladox none34da15c2017-07-01 14:49:10 +00002346TagWebLinks will appear in the tag list in the last column.
2347
Dave Borowitzd0c01fd2017-06-06 10:47:08 -04002348If a `get*WebLink` implementation returns `null`, the link will be omitted. This
2349allows the plugin to selectively "enable" itself on a per-project/branch/file
2350basis.
2351
Saša Živkovca7a67e2015-12-01 14:25:10 +01002352[[lfs-extension]]
2353== LFS Storage Plugins
2354
David Pursehouse2463c542016-08-02 16:04:58 +09002355Gerrit provides an extension point that enables development of
2356link:https://github.com/github/git-lfs/blob/master/docs/api/v1/http-v1-batch.md[
2357LFS (Large File Storage)] storage plugins. Gerrit core exposes the default LFS
2358protocol endpoint `<project-name>/info/lfs/objects/batch` and forwards the requests
2359to the configured link:config-gerrit.html#lfs[lfs.plugin] plugin which implements
2360the LFS protocol. By exposing the default LFS endpoint, the git-lfs client can be
2361used without any configuration.
Saša Živkovca7a67e2015-12-01 14:25:10 +01002362
2363[source, java]
2364----
2365/** Provide an LFS protocol implementation */
2366import org.eclipse.jgit.lfs.server.LargeFileRepository;
2367import org.eclipse.jgit.lfs.server.LfsProtocolServlet;
2368
2369@Singleton
2370public class LfsApiServlet extends LfsProtocolServlet {
2371 private static final long serialVersionUID = 1L;
2372
2373 private final S3LargeFileRepository repository;
2374
2375 @Inject
2376 LfsApiServlet(S3LargeFileRepository repository) {
2377 this.repository = repository;
2378 }
2379
2380 @Override
2381 protected LargeFileRepository getLargeFileRepository() {
2382 return repository;
2383 }
2384}
2385
2386/** Register the LfsApiServlet to listen on the default LFS protocol endpoint */
2387import static com.google.gerrit.httpd.plugins.LfsPluginServlet.URL_REGEX;
2388
2389import com.google.gerrit.httpd.plugins.HttpPluginModule;
2390
2391public class HttpModule extends HttpPluginModule {
2392
2393 @Override
2394 protected void configureServlets() {
2395 serveRegex(URL_REGEX).with(LfsApiServlet.class);
2396 }
2397}
2398
2399/** Provide an implementation of the LargeFileRepository */
2400import org.eclipse.jgit.lfs.server.s3.S3Repository;
2401
2402public class S3LargeFileRepository extends S3Repository {
2403...
2404}
2405----
2406
David Pursehouse8ad11732016-08-29 15:00:14 +09002407[[metrics]]
2408== Metrics
2409
2410=== Metrics Reporting
2411
2412To send Gerrit's metrics data to an external reporting backend, a plugin can
2413get a `MetricRegistry` injected and register an instance of a class that
2414implements the `Reporter` interface from link:http://metrics.dropwizard.io/[
2415DropWizard Metrics].
2416
2417Metric reporting plugin implementations are provided for
2418link:https://gerrit.googlesource.com/plugins/metrics-reporter-jmx/[JMX],
2419link:https://gerrit.googlesource.com/plugins/metrics-reporter-elasticsearch/[Elastic Search],
2420and link:https://gerrit.googlesource.com/plugins/metrics-reporter-graphite/[Graphite].
2421
2422There is also a working example of reporting metrics to the console in the
Eryk Szymanskida073b12017-09-11 14:27:57 +02002423link: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 +09002424cookbook plugin].
2425
2426=== Providing own metrics
2427
2428Plugins may provide metrics to be dispatched to external reporting services by
2429getting a `MetricMaker` injected and creating instances of specific types of
2430metric:
2431
2432* Counter
2433+
2434Metric whose value increments during the life of the process.
2435
2436* Timer
2437+
2438Metric recording time spent on an operation.
2439
2440* Histogram
2441+
2442Metric recording statistical distribution (rate) of values.
2443
David Pursehouse48d05ea2017-02-03 19:05:29 +09002444Note that metrics cannot be recorded from plugin init steps that
2445are run during site initialization.
2446
David Pursehousec3bbd562017-02-06 20:25:29 +09002447By default, plugin metrics are recorded under
2448`plugins/${plugin-name}/${metric-name}`. This can be changed by
2449setting `plugins.${plugin-name}.metricsPrefix` in the `gerrit.config`
2450file. For example:
2451
2452----
2453 [plugin "my-plugin"]
2454 metricsPrefix = my-metrics
2455----
2456
2457will cause the metrics to be recorded under `my-metrics/${metric-name}`.
David Pursehouse8ad11732016-08-29 15:00:14 +09002458
2459See the replication metrics in the
2460link:https://gerrit.googlesource.com/plugins/replication/+/master/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationMetrics.java[
2461replication plugin] for an example of usage.
2462
Edwin Kempinda17bc32016-06-14 11:50:58 +02002463[[account-patch-review-store]]
2464== AccountPatchReviewStore
2465
2466The AccountPatchReviewStore is used to store reviewed flags on changes.
2467A reviewed flag is a tuple of (patch set ID, file, account ID) and
2468records whether the user has reviewed a file in a patch set. Each user
2469can easily have thousands of reviewed flags and the number of reviewed
2470flags is growing without bound. The store must be able handle this data
2471volume efficiently.
2472
2473Gerrit implements this extension point, but plugins may bind another
2474implementation, e.g. one that supports multi-master.
2475
2476----
2477DynamicItem.bind(binder(), AccountPatchReviewStore.class)
2478 .to(MultiMasterAccountPatchReviewStore.class);
2479
2480...
2481
2482public class MultiMasterAccountPatchReviewStore
2483 implements AccountPatchReviewStore {
2484 ...
2485}
2486----
2487
Dave Borowitzf1790ce2016-11-14 12:26:12 -08002488
Edwin Kempinf5a77332012-07-18 11:17:53 +02002489[[documentation]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002490== Documentation
Nasser Grainawie033b262012-05-09 17:54:21 -07002491
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002492If a plugin does not register a filter or servlet to handle URLs
Jonathan Nieder5758f182015-03-30 11:28:55 -07002493`+/Documentation/*+` or `+/static/*+`, the core Gerrit server will
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002494automatically export these resources over HTTP from the plugin JAR.
2495
David Pursehouse6853b5a2013-07-10 11:38:03 +09002496Static resources under the `static/` directory in the JAR will be
Dave Borowitzb893ac82013-03-27 10:03:55 -04002497available as `/plugins/helloworld/static/resource`. This prefix is
2498configurable by setting the `Gerrit-HttpStaticPrefix` attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002499
David Pursehouse6853b5a2013-07-10 11:38:03 +09002500Documentation files under the `Documentation/` directory in the JAR
Dave Borowitzb893ac82013-03-27 10:03:55 -04002501will be available as `/plugins/helloworld/Documentation/resource`. This
2502prefix is configurable by setting the `Gerrit-HttpDocumentationPrefix`
2503attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002504
Christian Aistleitner040cf822015-03-26 21:09:09 +01002505Documentation may be written in the Markdown flavor
2506link:https://github.com/sirthias/pegdown[pegdown]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002507if the file name ends with `.md`. Gerrit will automatically convert
2508Markdown to HTML if accessed with extension `.html`.
Nasser Grainawie033b262012-05-09 17:54:21 -07002509
Edwin Kempinf5a77332012-07-18 11:17:53 +02002510[[macros]]
Edwin Kempinc78777d2012-07-16 15:55:11 +02002511Within the Markdown documentation files macros can be used that allow
2512to write documentation with reasonably accurate examples that adjust
2513automatically based on the installation.
2514
2515The following macros are supported:
2516
2517[width="40%",options="header"]
2518|===================================================
2519|Macro | Replacement
2520|@PLUGIN@ | name of the plugin
2521|@URL@ | Gerrit Web URL
2522|@SSH_HOST@ | SSH Host
2523|@SSH_PORT@ | SSH Port
2524|===================================================
2525
2526The macros will be replaced when the documentation files are rendered
2527from Markdown to HTML.
2528
2529Macros that start with `\` such as `\@KEEP@` will render as `@KEEP@`
2530even if there is an expansion for `KEEP` in the future.
2531
Edwin Kempinf5a77332012-07-18 11:17:53 +02002532[[auto-index]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002533=== Automatic Index
Shawn O. Pearce795167c2012-05-12 11:20:18 -07002534
2535If a plugin does not handle its `/` URL itself, Gerrit will
2536redirect clients to the plugin's `/Documentation/index.html`.
2537Requests for `/Documentation/` (bare directory) will also redirect
2538to `/Documentation/index.html`.
2539
2540If neither resource `Documentation/index.html` or
2541`Documentation/index.md` exists in the plugin JAR, Gerrit will
2542automatically generate an index page for the plugin's documentation
2543tree by scanning every `*.md` and `*.html` file in the Documentation/
2544directory.
2545
2546For any discovered Markdown (`*.md`) file, Gerrit will parse the
2547header of the file and extract the first level one title. This
2548title text will be used as display text for a link to the HTML
2549version of the page.
2550
2551For any discovered HTML (`*.html`) file, Gerrit will use the name
2552of the file, minus the `*.html` extension, as the link text. Any
2553hyphens in the file name will be replaced with spaces.
2554
David Pursehouse6853b5a2013-07-10 11:38:03 +09002555If a discovered file is named `about.md` or `about.html`, its
2556content will be inserted in an 'About' section at the top of the
2557auto-generated index page. If both `about.md` and `about.html`
2558exist, only the first discovered file will be used.
2559
Shawn O. Pearce795167c2012-05-12 11:20:18 -07002560If a discovered file name beings with `cmd-` it will be clustered
David Pursehouse6853b5a2013-07-10 11:38:03 +09002561into a 'Commands' section of the generated index page.
2562
David Pursehousefe529152013-08-14 16:35:06 +09002563If a discovered file name beings with `servlet-` it will be clustered
2564into a 'Servlets' section of the generated index page.
2565
2566If a discovered file name beings with `rest-api-` it will be clustered
2567into a 'REST APIs' section of the generated index page.
2568
David Pursehouse6853b5a2013-07-10 11:38:03 +09002569All other files are clustered under a 'Documentation' section.
Shawn O. Pearce795167c2012-05-12 11:20:18 -07002570
2571Some optional information from the manifest is extracted and
2572displayed as part of the index page, if present in the manifest:
2573
2574[width="40%",options="header"]
2575|===================================================
2576|Field | Source Attribute
2577|Name | Implementation-Title
2578|Vendor | Implementation-Vendor
2579|Version | Implementation-Version
2580|URL | Implementation-URL
2581|API Version | Gerrit-ApiVersion
2582|===================================================
2583
Edwin Kempinf5a77332012-07-18 11:17:53 +02002584[[deployment]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002585== Deployment
Nasser Grainawie033b262012-05-09 17:54:21 -07002586
Edwin Kempinf7295742012-07-16 15:03:46 +02002587Compiled plugins and extensions can be deployed to a running Gerrit
2588server using the link:cmd-plugin-install.html[plugin install] command.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002589
David Pursehouse00c70812017-07-31 10:57:26 +01002590Web UI plugins distributed as a single `.js` file (or `.html' file for
2591Polygerrit) can be deployed without the overhead of JAR packaging. For
2592more information refer to link:cmd-plugin-install.html[plugin install]
2593command.
Dariusz Luksza357a2422012-11-12 06:16:26 +01002594
David Pursehouse75021fe2017-07-31 23:07:04 +02002595Plugins can also be copied directly into the server's directory at
2596`$site_path/plugins/$name.(jar|js|html)`. For Web UI plugins, the name
2597of the file, minus the `.js` or `.html` extension, will be used as the
2598plugin name. For JAR plugins, the value of the `Gerrit-PluginName`
2599manifest attribute will be used, if provided, otherwise the name of
2600the file, minus the `.jar` extension, will be used.
2601
2602For Web UI plugins, the plugin version is derived from the filename.
2603If the filename contains one or more hyphens, the version is taken
2604from the portion following the last hyphen. For example if the plugin
2605filename is `my-plugin-1.0.js` the version will be `1.0`. For JAR
2606plugins, the version is taken from the `Version` attribute in the
2607manifest.
2608
2609Unless disabled, servers periodically scan the `$site_path/plugins`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002610directory for updated plugins. The time can be adjusted by
2611link:config-gerrit.html#plugins.checkFrequency[plugins.checkFrequency].
Deniz Türkoglueb78b602012-05-07 14:02:36 -07002612
Edwin Kempinf7295742012-07-16 15:03:46 +02002613For disabling plugins the link:cmd-plugin-remove.html[plugin remove]
2614command can be used.
2615
Brad Larsond5e87c32012-07-11 12:18:49 -05002616Disabled plugins can be re-enabled using the
2617link:cmd-plugin-enable.html[plugin enable] command.
2618
Edwin Kempinc1a25102015-06-22 14:47:36 +02002619== Known issues and bugs
2620
2621=== Error handling in UI when using the REST API
2622
2623When a plugin invokes a REST endpoint in the UI, it provides an
2624`AsyncCallback` to handle the result. At the moment the
2625`onFailure(Throwable)` of the callback is never invoked, even if there
2626is an error. Errors are always handled by the Gerrit core UI which
2627shows the error dialog. This means currently plugins cannot do any
2628error handling and e.g. ignore expected errors.
2629
Han-Wen Nienhuys84d830b2017-02-15 16:36:04 +01002630In the following example the REST endpoint would return '404 Not
2631Found' if the user has no username and the Gerrit core UI would
2632display an error dialog for this. However having no username is
2633not an error and the plugin may like to handle this case.
Edwin Kempinc1a25102015-06-22 14:47:36 +02002634
2635[source,java]
2636----
Han-Wen Nienhuys84d830b2017-02-15 16:36:04 +01002637new RestApi("accounts").id("self").view("username")
Edwin Kempinc1a25102015-06-22 14:47:36 +02002638 .get(new AsyncCallback<NativeString>() {
2639
2640 @Override
Han-Wen Nienhuys84d830b2017-02-15 16:36:04 +01002641 public void onSuccess(NativeString username) {
Edwin Kempinc1a25102015-06-22 14:47:36 +02002642 // TODO
2643 }
2644
2645 @Override
2646 public void onFailure(Throwable caught) {
2647 // never invoked
2648 }
2649});
2650----
2651
2652
Patrick Hiesel87880b02016-05-03 18:15:08 +02002653[[reviewer-suggestion]]
2654== Reviewer Suggestion Plugins
2655
2656Gerrit provides an extension point that enables Plugins to rank
2657the list of reviewer suggestion a user receives upon clicking "Add Reviewer" on
2658the change screen.
2659Gerrit supports both a default suggestion that appears when the user has not yet
2660typed anything and a filtered suggestion that is shown as the user starts
2661typing.
2662Plugins receive a candidate list and can return a Set of suggested reviewers
2663containing the Account.Id and a score for each reviewer.
2664The candidate list is non-binding and plugins can choose to return reviewers not
2665initially contained in the candidate list.
2666Server administrators can configure the overall weight of each plugin using the
2667weight config parameter on [addreviewer "<pluginName-exportName>"].
2668
2669[source, java]
2670----
Patrick Hiesel2514e412016-10-13 09:34:44 +02002671import com.google.gerrit.common.Nullable;
2672import com.google.gerrit.extensions.annotations.ExtensionPoint;
Patrick Hiesel87880b02016-05-03 18:15:08 +02002673import com.google.gerrit.reviewdb.client.Account;
2674import com.google.gerrit.reviewdb.client.Change;
Patrick Hiesel2514e412016-10-13 09:34:44 +02002675import com.google.gerrit.reviewdb.client.Project;
Patrick Hiesel87880b02016-05-03 18:15:08 +02002676
2677import java.util.Set;
2678
2679public class MyPlugin implements ReviewerSuggestion {
Patrick Hiesel2514e412016-10-13 09:34:44 +02002680 public Set<SuggestedReviewer> suggestReviewers(Project.NameKey project,
2681 @Nullable Change.Id changeId, @Nullable String query,
2682 Set<Account.Id> candidates) {
2683 Set<SuggestedReviewer> suggestions = new HashSet<>();
2684 // Implement your ranking logic here
2685 return suggestions;
Patrick Hiesel87880b02016-05-03 18:15:08 +02002686 }
2687}
2688----
2689
2690
Patrick Hiesel30c76182017-01-20 11:46:43 +01002691[[mail-filter]]
2692== Mail Filter Plugins
2693
2694Gerrit provides an extension point that enables Plugins to discard incoming
2695messages and prevent further processing by Gerrit.
2696
David Pursehouse4b067752017-03-03 15:54:53 +09002697This can be used to implement spam checks, signature validations or organization
Patrick Hiesel30c76182017-01-20 11:46:43 +01002698specific checks like IP filters.
2699
2700[source, java]
2701----
2702import com.google.gerrit.extensions.annotations.ExtensionPoint;
2703import com.google.gerrit.server.mail.receive.MailMessage;
2704
2705public class MyPlugin implements MailFilter {
2706 boolean shouldProcessMessage(MailMessage message) {
2707 // Implement your filter logic here
2708 return true;
2709 }
2710}
2711----
2712
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002713== SEE ALSO
David Ostrovskyf86bae52013-09-01 09:10:39 +02002714
2715* link:js-api.html[JavaScript API]
2716* link:dev-rest-api.html[REST API Developers' Notes]
2717
Deniz Türkoglueb78b602012-05-07 14:02:36 -07002718GERRIT
2719------
2720Part of link:index.html[Gerrit Code Review]
Yuxuan 'fishy' Wang99cb68d2013-10-31 17:26:00 -07002721
2722SEARCHBOX
2723---------