blob: dada6233dde967168480451c372cda602ecffdf6 [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
Viktar Donich5055e8d2017-11-09 13:02:42 -08006For PolyGerrit-specific plugin development, consult with
7link:pg-plugin-dev.html[PolyGerrit Plugin Development] guide.
8
Edwin Kempinaf275322012-07-16 11:04:01 +02009Depending on how tightly the extension code is coupled with the Gerrit
10server code, there is a distinction between `plugins` and `extensions`.
11
Edwin Kempinf5a77332012-07-18 11:17:53 +020012[[plugin]]
Edwin Kempin948de0f2012-07-16 10:34:35 +020013A `plugin` in Gerrit is tightly coupled code that runs in the same
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070014JVM as Gerrit. It has full access to all server internals. Plugins
15are tightly coupled to a specific major.minor server version and
16may require source code changes to compile against a different
17server version.
18
Luca Milanesio86b9b6c2017-08-09 09:54:36 +010019Plugins may require a specific major.minor.patch server version
20and may need rebuild and revalidation across different
21patch levels. A different patch level may only add new
22API interfaces and never change or extend existing ones.
23
Edwin Kempinf5a77332012-07-18 11:17:53 +020024[[extension]]
Edwin Kempin948de0f2012-07-16 10:34:35 +020025An `extension` in Gerrit runs inside of the same JVM as Gerrit
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070026in the same way as a plugin, but has limited visibility to the
Edwin Kempinfd19bfb2012-07-16 10:44:17 +020027server's internals. The limited visibility reduces the extension's
28dependencies, enabling it to be compatible across a wider range
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070029of server versions.
30
31Most of this documentation refers to either type as a plugin.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070032
Edwin Kempinf878c4b2012-07-18 09:34:25 +020033[[getting-started]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -080034== Getting started
Deniz Türkoglueb78b602012-05-07 14:02:36 -070035
David Ostrovskya052e522016-12-10 17:53:16 +010036To get started with the development of a plugin clone the sample
37plugin:
David Pursehousecf2e9002017-03-01 19:10:43 +090038
Dave Borowitz5cc8f662012-05-21 09:51:36 -070039----
David Pursehouse2cf0cb52013-08-27 16:09:53 +090040$ git clone https://gerrit.googlesource.com/plugins/cookbook-plugin
Dave Borowitz5cc8f662012-05-21 09:51:36 -070041----
David Pursehousecf2e9002017-03-01 19:10:43 +090042
43This is a project that demonstrates the various features of the
44plugin API. It can be taken as an example to develop an own plugin.
45
Edwin Kempinf878c4b2012-07-18 09:34:25 +020046When starting from this example one should take care to adapt the
David Ostrovskya052e522016-12-10 17:53:16 +010047`Gerrit-ApiVersion` in the `BUILD` to the version of Gerrit for which
48the plugin is developed.
Dave Borowitz5cc8f662012-05-21 09:51:36 -070049
Edwin Kempinf878c4b2012-07-18 09:34:25 +020050[[API]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -080051== API
Edwin Kempinf878c4b2012-07-18 09:34:25 +020052
53There are two different API formats offered against which plugins can
54be developed:
Deniz Türkoglueb78b602012-05-07 14:02:36 -070055
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070056gerrit-extension-api.jar::
57 A stable but thin interface. Suitable for extensions that need
58 to be notified of events, but do not require tight coupling to
59 the internals of Gerrit. Extensions built against this API can
60 expect to be binary compatible across a wide range of server
61 versions.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070062
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070063gerrit-plugin-api.jar::
64 The complete internals of the Gerrit server, permitting a
65 plugin to tightly couple itself and provide additional
66 functionality that is not possible as an extension. Plugins
67 built against this API are expected to break at the source
68 code level between every major.minor Gerrit release. A plugin
69 that compiles against 2.5 will probably need source code level
70 changes to work with 2.6, 2.7, and so on.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070071
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -080072== Manifest
Deniz Türkoglueb78b602012-05-07 14:02:36 -070073
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070074Plugins may provide optional description information with standard
75manifest fields:
Nasser Grainawie033b262012-05-09 17:54:21 -070076
Michael Ochmannb99feab2016-07-06 14:10:22 +020077----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070078 Implementation-Title: Example plugin showing examples
79 Implementation-Version: 1.0
80 Implementation-Vendor: Example, Inc.
Michael Ochmannb99feab2016-07-06 14:10:22 +020081----
Nasser Grainawie033b262012-05-09 17:54:21 -070082
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -080083=== ApiType
Nasser Grainawie033b262012-05-09 17:54:21 -070084
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070085Plugins using the tightly coupled `gerrit-plugin-api.jar` must
86declare this API dependency in the manifest to gain access to server
Edwin Kempin948de0f2012-07-16 10:34:35 +020087internals. If no `Gerrit-ApiType` is specified the stable `extension`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070088API will be assumed. This may cause ClassNotFoundExceptions when
89loading a plugin that needs the plugin API.
Nasser Grainawie033b262012-05-09 17:54:21 -070090
Michael Ochmannb99feab2016-07-06 14:10:22 +020091----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070092 Gerrit-ApiType: plugin
Michael Ochmannb99feab2016-07-06 14:10:22 +020093----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070094
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -080095=== Explicit Registration
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070096
97Plugins that use explicit Guice registration must name the Guice
98modules in the manifest. Up to three modules can be named in the
Edwin Kempin948de0f2012-07-16 10:34:35 +020099manifest. `Gerrit-Module` supplies bindings to the core server;
100`Gerrit-SshModule` supplies SSH commands to the SSH server (if
101enabled); `Gerrit-HttpModule` supplies servlets and filters to the HTTP
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700102server (if enabled). If no modules are named automatic registration
103will be performed by scanning all classes in the plugin JAR for
104`@Listen` and `@Export("")` annotations.
105
Michael Ochmannb99feab2016-07-06 14:10:22 +0200106----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700107 Gerrit-Module: tld.example.project.CoreModuleClassName
108 Gerrit-SshModule: tld.example.project.SshModuleClassName
109 Gerrit-HttpModule: tld.example.project.HttpModuleClassName
Michael Ochmannb99feab2016-07-06 14:10:22 +0200110----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700111
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200112[[plugin_name]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800113=== Plugin Name
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200114
David Pursehoused128c892013-10-22 21:52:21 +0900115A plugin can optionally provide its own plugin name.
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200116
Michael Ochmannb99feab2016-07-06 14:10:22 +0200117----
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200118 Gerrit-PluginName: replication
Michael Ochmannb99feab2016-07-06 14:10:22 +0200119----
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200120
121This is useful for plugins that contribute plugin-owned capabilities that
122are stored in the `project.config` file. Another use case is to be able to put
123project specific plugin configuration section in `project.config`. In this
124case it is advantageous to reserve the plugin name to access the configuration
125section in the `project.config` file.
126
127If `Gerrit-PluginName` is omitted, then the plugin's name is determined from
128the plugin file name.
129
130If a plugin provides its own name, then that plugin cannot be deployed
131multiple times under different file names on one Gerrit site.
132
133For Maven driven plugins, the following line must be included in the pom.xml
134file:
135
136[source,xml]
137----
138<manifestEntries>
139 <Gerrit-PluginName>name</Gerrit-PluginName>
140</manifestEntries>
141----
142
David Ostrovskyfdbfcad2016-11-15 06:35:29 -0800143For Bazel driven plugins, the following line must be included in the BUILD
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200144configuration file:
145
146[source,python]
147----
David Pursehouse529ec252013-09-27 13:45:14 +0900148manifest_entries = [
149 'Gerrit-PluginName: name',
150]
David Ostrovsky366ad0e2013-09-05 19:59:09 +0200151----
152
Edwin Kempinc0b1b0e2013-10-01 14:13:54 +0200153A plugin can get its own name injected at runtime:
154
155[source,java]
156----
157public class MyClass {
158
159 private final String pluginName;
160
161 @Inject
162 public MyClass(@PluginName String pluginName) {
163 this.pluginName = pluginName;
164 }
165
David Pursehoused128c892013-10-22 21:52:21 +0900166 [...]
Edwin Kempinc0b1b0e2013-10-01 14:13:54 +0200167}
168----
169
David Pursehouse8ed0d922013-10-18 18:57:56 +0900170A plugin can get its canonical web URL injected at runtime:
171
172[source,java]
173----
174public class MyClass {
175
176 private final String url;
177
178 @Inject
179 public MyClass(@PluginCanonicalWebUrl String url) {
180 this.url = url;
181 }
182
183 [...]
184}
185----
186
187The URL is composed of the server's canonical web URL and the plugin's
188name, i.e. `http://review.example.com:8080/plugin-name`.
189
190The canonical web URL may be injected into any .jar plugin regardless of
191whether or not the plugin provides an HTTP servlet.
192
Edwin Kempinf7295742012-07-16 15:03:46 +0200193[[reload_method]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800194=== Reload Method
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700195
196If a plugin holds an exclusive resource that must be released before
197loading the plugin again (for example listening on a network port or
Edwin Kempin948de0f2012-07-16 10:34:35 +0200198acquiring a file lock) the manifest must declare `Gerrit-ReloadMode`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700199to be `restart`. Otherwise the preferred method of `reload` will
200be used, as it enables the server to hot-patch an updated plugin
201with no down time.
202
Michael Ochmannb99feab2016-07-06 14:10:22 +0200203----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700204 Gerrit-ReloadMode: restart
Michael Ochmannb99feab2016-07-06 14:10:22 +0200205----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700206
207In either mode ('restart' or 'reload') any plugin or extension can
208be updated without restarting the Gerrit server. The difference is
209how Gerrit handles the upgrade:
210
211restart::
212 The old plugin is completely stopped. All registrations of SSH
213 commands and HTTP servlets are removed. All registrations of any
214 extension points are removed. All registered LifecycleListeners
215 have their `stop()` method invoked in reverse order. The new
216 plugin is started, and registrations are made from the new
217 plugin. There is a brief window where neither the old nor the
218 new plugin is connected to the server. This means SSH commands
219 and HTTP servlets will return not found errors, and the plugin
220 will not be notified of events that occurred during the restart.
221
222reload::
223 The new plugin is started. Its LifecycleListeners are permitted
224 to perform their `start()` methods. All SSH and HTTP registrations
225 are atomically swapped out from the old plugin to the new plugin,
226 ensuring the server never returns a not found error. All extension
227 point listeners are atomically swapped out from the old plugin to
228 the new plugin, ensuring no events are missed (however some events
229 may still route to the old plugin if the swap wasn't complete yet).
230 The old plugin is stopped.
231
Edwin Kempinf7295742012-07-16 15:03:46 +0200232To reload/restart a plugin the link:cmd-plugin-reload.html[plugin reload]
233command can be used.
234
Luca Milanesio737285d2012-09-25 14:26:43 +0100235[[init_step]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800236=== Init step
Luca Milanesio737285d2012-09-25 14:26:43 +0100237
238Plugins can contribute their own "init step" during the Gerrit init
239wizard. This is useful for guiding the Gerrit administrator through
David Pursehouse659860f2013-12-16 14:50:04 +0900240the settings needed by the plugin to work properly.
Luca Milanesio737285d2012-09-25 14:26:43 +0100241
242For instance plugins to integrate Jira issues to Gerrit changes may
243contribute their own "init step" to allow configuring the Jira URL,
244credentials and possibly verify connectivity to validate them.
245
Michael Ochmannb99feab2016-07-06 14:10:22 +0200246----
Luca Milanesio737285d2012-09-25 14:26:43 +0100247 Gerrit-InitStep: tld.example.project.MyInitStep
Michael Ochmannb99feab2016-07-06 14:10:22 +0200248----
Luca Milanesio737285d2012-09-25 14:26:43 +0100249
250MyInitStep needs to follow the standard Gerrit InitStep syntax
David Pursehouse92463562013-06-24 10:16:28 +0900251and behavior: writing to the console using the injected ConsoleUI
Luca Milanesio737285d2012-09-25 14:26:43 +0100252and accessing / changing configuration settings using Section.Factory.
253
254In addition to the standard Gerrit init injections, plugins receive
255the @PluginName String injection containing their own plugin name.
256
Edwin Kempind4cfac12013-11-27 11:22:34 +0100257During their initialization plugins may get access to the
258`project.config` file of the `All-Projects` project and they are able
259to store configuration parameters in it. For this a plugin `InitStep`
Jiří Engelthaler3033a0a2015-02-16 09:44:32 +0100260can get `com.google.gerrit.pgm.init.api.AllProjectsConfig` injected:
Edwin Kempind4cfac12013-11-27 11:22:34 +0100261
262[source,java]
263----
264 public class MyInitStep implements InitStep {
265 private final String pluginName;
266 private final ConsoleUI ui;
267 private final AllProjectsConfig allProjectsConfig;
268
Doug Kelly732ad202015-11-13 13:11:32 -0800269 @Inject
Edwin Kempind4cfac12013-11-27 11:22:34 +0100270 public MyInitStep(@PluginName String pluginName, ConsoleUI ui,
271 AllProjectsConfig allProjectsConfig) {
272 this.pluginName = pluginName;
273 this.ui = ui;
274 this.allProjectsConfig = allProjectsConfig;
275 }
276
277 @Override
278 public void run() throws Exception {
Edwin Kempin93e7d5d2014-01-03 09:53:20 +0100279 }
280
281 @Override
282 public void postRun() throws Exception {
Edwin Kempind4cfac12013-11-27 11:22:34 +0100283 ui.message("\n");
284 ui.header(pluginName + " Integration");
285 boolean enabled = ui.yesno(true, "By default enabled for all projects");
Adrian Görlerd1612972014-10-20 17:06:07 +0200286 Config cfg = allProjectsConfig.load().getConfig();
Edwin Kempind4cfac12013-11-27 11:22:34 +0100287 if (enabled) {
288 cfg.setBoolean("plugin", pluginName, "enabled", enabled);
289 } else {
290 cfg.unset("plugin", pluginName, "enabled");
291 }
292 allProjectsConfig.save(pluginName, "Initialize " + pluginName + " Integration");
293 }
294 }
295----
296
Luca Milanesio737285d2012-09-25 14:26:43 +0100297Bear in mind that the Plugin's InitStep class will be loaded but
298the standard Gerrit runtime environment is not available and the plugin's
299own Guice modules were not initialized.
300This means the InitStep for a plugin is not executed in the same way that
301the plugin executes within the server, and may mean a plugin author cannot
302trivially reuse runtime code during init.
303
304For instance a plugin that wants to verify connectivity may need to statically
305call the constructor of their connection class, passing in values obtained
306from the Section.Factory rather than from an injected Config object.
307
David Pursehoused128c892013-10-22 21:52:21 +0900308Plugins' InitSteps are executed during the "Gerrit Plugin init" phase, after
309the extraction of the plugins embedded in the distribution .war file into
310`$GERRIT_SITE/plugins` and before the DB Schema initialization or upgrade.
311
312A plugin's InitStep cannot refer to Gerrit's DB Schema or any other Gerrit
313runtime objects injected at startup.
Luca Milanesio737285d2012-09-25 14:26:43 +0100314
David Pursehouse68153d72013-09-04 10:09:17 +0900315[source,java]
316----
317public class MyInitStep implements InitStep {
318 private final ConsoleUI ui;
319 private final Section.Factory sections;
320 private final String pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100321
David Pursehouse68153d72013-09-04 10:09:17 +0900322 @Inject
323 public GitBlitInitStep(final ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) {
324 this.ui = ui;
325 this.sections = sections;
326 this.pluginName = pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100327 }
David Pursehouse68153d72013-09-04 10:09:17 +0900328
329 @Override
330 public void run() throws Exception {
331 ui.header("\nMy plugin");
332
333 Section mySection = getSection("myplugin", null);
334 mySection.string("Link name", "linkname", "MyLink");
335 }
Edwin Kempin93e7d5d2014-01-03 09:53:20 +0100336
337 @Override
338 public void postRun() throws Exception {
339 }
David Pursehouse68153d72013-09-04 10:09:17 +0900340}
341----
Luca Milanesio737285d2012-09-25 14:26:43 +0100342
Edwin Kempinf5a77332012-07-18 11:17:53 +0200343[[classpath]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800344== Classpath
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700345
346Each plugin is loaded into its own ClassLoader, isolating plugins
347from each other. A plugin or extension inherits the Java runtime
348and the Gerrit API chosen by `Gerrit-ApiType` (extension or plugin)
349from the hosting server.
350
351Plugins are loaded from a single JAR file. If a plugin needs
352additional libraries, it must include those dependencies within
353its own JAR. Plugins built using Maven may be able to use the
354link:http://maven.apache.org/plugins/maven-shade-plugin/[shade plugin]
355to package additional dependencies. Relocating (or renaming) classes
356should not be necessary due to the ClassLoader isolation.
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700357
Edwin Kempin98202662013-09-18 16:03:03 +0200358[[events]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800359== Listening to Events
Edwin Kempin98202662013-09-18 16:03:03 +0200360
361Certain operations in Gerrit trigger events. Plugins may receive
362notifications of these events by implementing the corresponding
363listeners.
364
Martin Fick4c72aea2014-12-10 14:58:12 -0700365* `com.google.gerrit.common.EventListener`:
Edwin Kempin64059f52013-10-31 13:49:25 +0100366+
Hugo Arèsbc1093d2016-02-23 15:04:50 -0500367Allows to listen to events without user visibility restrictions. These
368are the same link:cmd-stream-events.html#events[events] that are also streamed by
Edwin Kempin64059f52013-10-31 13:49:25 +0100369the link:cmd-stream-events.html[gerrit stream-events] command.
370
Hugo Arèsbc1093d2016-02-23 15:04:50 -0500371* `com.google.gerrit.common.UserScopedEventListener`:
372+
373Allows to listen to events visible to the specified user. These are the
374same link:cmd-stream-events.html#events[events] that are also streamed
375by the link:cmd-stream-events.html[gerrit stream-events] command.
376
Edwin Kempin98202662013-09-18 16:03:03 +0200377* `com.google.gerrit.extensions.events.LifecycleListener`:
378+
Edwin Kempin3e7928a2013-12-03 07:39:00 +0100379Plugin start and stop
Edwin Kempin98202662013-09-18 16:03:03 +0200380
381* `com.google.gerrit.extensions.events.NewProjectCreatedListener`:
382+
383Project creation
384
385* `com.google.gerrit.extensions.events.ProjectDeletedListener`:
386+
387Project deletion
388
Edwin Kempinb27c9392013-11-19 13:12:43 +0100389* `com.google.gerrit.extensions.events.HeadUpdatedListener`:
390+
391Update of HEAD on a project
392
Stefan Lay310d77d2014-05-28 13:45:25 +0200393* `com.google.gerrit.extensions.events.UsageDataPublishedListener`:
394+
395Publication of usage data
396
Adrian Görlerf4a4c9a2014-08-22 17:09:18 +0200397* `com.google.gerrit.extensions.events.GarbageCollectorListener`:
398+
399Garbage collection ran on a project
400
Hector Oswaldo Caballero5fbbdad2015-11-11 14:30:46 -0500401* `com.google.gerrit.server.extensions.events.ChangeIndexedListener`:
402+
Hugo Arès682171f2017-04-24 13:44:43 +0200403Update of the change secondary index
404
405* `com.google.gerrit.server.extensions.events.AccountIndexedListener`:
406+
407Update of the account secondary index
Hector Oswaldo Caballero5fbbdad2015-11-11 14:30:46 -0500408
Hugo Arèsee788ddb2017-05-08 10:23:45 -0400409* `com.google.gerrit.server.extensions.events.GroupIndexedListener`:
410+
411Update of the group secondary index
412
Xin Sun97169862017-07-13 17:31:16 -0700413* `com.google.gerrit.server.extensions.events.ProjectIndexedListener`:
414+
415Update of the project secondary index
416
Luca Milanesio45da6182016-05-12 11:33:30 +0100417* `com.google.gerrit.httpd.WebLoginListener`:
418+
419User login or logout interactively on the Web user interface.
420
421The event listener is under the Gerrit http package to automatically
422inherit the javax.servlet.http dependencies and allowing to influence
423the login or logout flow with additional redirections.
424
Yang Zhenhui2659d422013-07-30 16:59:58 +0800425[[stream-events]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800426== Sending Events to the Events Stream
Yang Zhenhui2659d422013-07-30 16:59:58 +0800427
428Plugins may send events to the events stream where consumers of
429Gerrit's `stream-events` ssh command will receive them.
430
431To send an event, the plugin must invoke one of the `postEvent`
David Pursehousea9bf4762016-07-08 09:34:35 +0900432methods in the `EventDispatcher` interface, passing an instance of
Martin Fick4c72aea2014-12-10 14:58:12 -0700433its own custom event class derived from
434`com.google.gerrit.server.events.Event`.
Yang Zhenhui2659d422013-07-30 16:59:58 +0800435
David Pursehousea9bf4762016-07-08 09:34:35 +0900436[source,java]
437----
438import com.google.gerrit.common.EventDispatcher;
439import com.google.gerrit.extensions.registration.DynamicItem;
440import com.google.gwtorm.server.OrmException;
441import com.google.inject.Inject;
442
443class MyPlugin {
444 private final DynamicItem<EventDispatcher> eventDispatcher;
445
446 @Inject
447 myPlugin(DynamicItem<EventDispatcher> eventDispatcher) {
448 this.eventDispatcher = eventDispatcher;
449 }
450
451 private void postEvent(MyPluginEvent event) {
452 try {
453 eventDispatcher.get().postEvent(event);
454 } catch (OrmException e) {
455 // error handling
456 }
457 }
458}
459----
460
Martin Fick0aef6f12014-12-11 16:54:21 -0700461Plugins which define new Events should register them via the
462`com.google.gerrit.server.events.EventTypes.registerClass()`
463method. This will make the EventType known to the system.
David Pursehousea61ee502016-09-06 16:27:09 +0900464Deserializing events with the
Martin Fickf70c20a2014-12-11 17:03:15 -0700465`com.google.gerrit.server.events.EventDeserializer` class requires
466that the event be registered in EventTypes.
Martin Fick0aef6f12014-12-11 16:54:21 -0700467
Martin Fickecafc932014-12-15 14:09:41 -0700468== Modifying the Stream Event Flow
469
470It is possible to modify the stream event flow from plugins by registering
471an `com.google.gerrit.server.events.EventDispatcher`. A plugin may register
472a Dispatcher class to replace the internal Dispatcher. EventDispatcher is
473a DynamicItem, so Gerrit may only have one copy.
474
Edwin Kempin32737602014-01-23 09:04:58 +0100475[[validation]]
David Pursehouse91c5f5e2014-01-23 18:57:33 +0900476== Validation Listeners
Edwin Kempin32737602014-01-23 09:04:58 +0100477
478Certain operations in Gerrit can be validated by plugins by
479implementing the corresponding link:config-validation.html[listeners].
480
Andrii Shyshkalov6fdc8eb2016-11-29 17:45:01 +0100481[[change-message-modifier]]
482== Change Message Modifier
483
484`com.google.gerrit.server.git.ChangeMessageModifier`:
485plugins implementing this can modify commit message of the change being
486submitted by Rebase Always and Cherry Pick submit strategies as well as
487change being queried with COMMIT_FOOTERS option.
488
Edwin Kempin19e89c82017-10-11 12:00:51 +0200489[[merge-super-set-computation]]
490== Merge Super Set Computation
491
492The algorithm to compute the merge super set to detect changes that
493should be submitted together can be customized by implementing
494`com.google.gerrit.server.git.MergeSuperSetComputation`.
495MergeSuperSetComputation is a DynamicItem, so Gerrit may only have one
496implementation.
497
Saša Živkovec85a072014-01-28 10:08:25 +0100498[[receive-pack]]
499== Receive Pack Initializers
500
Dave Borowitzb8a2bae2017-10-03 10:34:28 +0100501Plugins may provide ReceivePackInitializer instances, which will be
502invoked by Gerrit just before a ReceivePack instance will be used.
503Usually, plugins will make use of the setXXX methods on the ReceivePack
504to set additional properties on it.
505
506The interactions with the core Gerrit ReceivePack initialization and
507between ReceivePackInitializers can be complex. Please read the
508ReceivePack Javadoc and Gerrit AsyncReceiveCommits implementation
509carefully.
Saša Živkovec85a072014-01-28 10:08:25 +0100510
Saša Živkov626c7312014-02-24 17:15:08 +0100511[[post-receive-hook]]
512== Post Receive-Pack Hooks
513
514Plugins may register PostReceiveHook instances in order to get
515notified when JGit successfully receives a pack. This may be useful
516for those plugins which would like to monitor changes in Git
517repositories.
518
Dave Borowitz223580f2017-10-03 09:55:51 +0100519[[upload-pack]]
520== Upload Pack Initializers
521
522Plugins may provide UploadPackInitializer instances, which will be
523invoked by Gerrit just before a UploadPack instance will be used.
524Usually, plugins will make use of the setXXX methods on the UploadPack
525to set additional properties on it.
526
527The interactions with the core Gerrit UploadPack initialization and
528between UploadPackInitializers can be complex. Please read the
529UploadPack Javadoc and Gerrit Upload/UploadFactory implementations
530carefully.
531
Hugo Arès572d5422014-06-17 14:22:03 -0400532[[pre-upload-hook]]
533== Pre Upload-Pack Hooks
534
535Plugins may register PreUploadHook instances in order to get
536notified when JGit is about to upload a pack. This may be useful
537for those plugins which would like to monitor usage in Git
538repositories.
539
Hugo Arès41b4c0d2016-08-02 15:26:57 -0400540[[post-upload-hook]]
541== Post Upload-Pack Hooks
542
543Plugins may register PostUploadHook instances in order to get notified after
544JGit is done uploading a pack.
545
Edwin Kempinf5a77332012-07-18 11:17:53 +0200546[[ssh]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800547== SSH Commands
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700548
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700549Plugins may provide commands that can be accessed through the SSH
550interface (extensions do not have this option).
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700551
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700552Command implementations must extend the base class SshCommand:
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700553
David Pursehouse68153d72013-09-04 10:09:17 +0900554[source,java]
555----
556import com.google.gerrit.sshd.SshCommand;
David Ostrovskyb7d97752013-11-09 05:23:26 +0100557import com.google.gerrit.sshd.CommandMetaData;
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700558
Ian Bulle1a12202014-02-16 17:15:42 -0800559@CommandMetaData(name="print", description="Print hello command")
David Pursehouse68153d72013-09-04 10:09:17 +0900560class PrintHello extends SshCommand {
Ian Bulle1a12202014-02-16 17:15:42 -0800561 @Override
562 protected void run() {
David Pursehouse68153d72013-09-04 10:09:17 +0900563 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700564 }
David Pursehouse68153d72013-09-04 10:09:17 +0900565}
566----
Nasser Grainawie033b262012-05-09 17:54:21 -0700567
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700568If no Guice modules are declared in the manifest, SSH commands may
Edwin Kempin948de0f2012-07-16 10:34:35 +0200569use auto-registration by providing an `@Export` annotation:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700570
David Pursehouse68153d72013-09-04 10:09:17 +0900571[source,java]
572----
573import com.google.gerrit.extensions.annotations.Export;
574import com.google.gerrit.sshd.SshCommand;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700575
David Pursehouse68153d72013-09-04 10:09:17 +0900576@Export("print")
577class PrintHello extends SshCommand {
Ian Bulle1a12202014-02-16 17:15:42 -0800578 @Override
579 protected void run() {
David Pursehouse68153d72013-09-04 10:09:17 +0900580 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700581 }
David Pursehouse68153d72013-09-04 10:09:17 +0900582}
583----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700584
585If explicit registration is being used, a Guice module must be
586supplied to register the SSH command and declared in the manifest
587with the `Gerrit-SshModule` attribute:
588
David Pursehouse68153d72013-09-04 10:09:17 +0900589[source,java]
590----
591import com.google.gerrit.sshd.PluginCommandModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700592
David Pursehouse68153d72013-09-04 10:09:17 +0900593class MyCommands extends PluginCommandModule {
Ian Bulle1a12202014-02-16 17:15:42 -0800594 @Override
David Pursehouse68153d72013-09-04 10:09:17 +0900595 protected void configureCommands() {
David Ostrovskyb7d97752013-11-09 05:23:26 +0100596 command(PrintHello.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700597 }
David Pursehouse68153d72013-09-04 10:09:17 +0900598}
599----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700600
601For a plugin installed as name `helloworld`, the command implemented
602by PrintHello class will be available to users as:
603
604----
Keunhong Parka09a6f12012-07-10 14:45:02 -0600605$ ssh -p 29418 review.example.com helloworld print
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700606----
607
David Ostrovsky79c4d892014-03-15 13:52:46 +0100608[[multiple-commands]]
609=== Multiple Commands bound to one implementation
610
David Ostrovskye3172b32013-10-13 14:19:13 +0200611Multiple SSH commands can be bound to the same implementation class. For
612example a Gerrit Shell plugin can bind different shell commands to the same
613implementation class:
614
615[source,java]
616----
617public class SshShellModule extends PluginCommandModule {
618 @Override
619 protected void configureCommands() {
620 command("ls").to(ShellCommand.class);
621 command("ps").to(ShellCommand.class);
622 [...]
623 }
624}
625----
626
627With the possible implementation:
628
629[source,java]
630----
631public class ShellCommand extends SshCommand {
632 @Override
633 protected void run() throws UnloggedFailure {
634 String cmd = getName().substring(getPluginName().length() + 1);
635 ProcessBuilder proc = new ProcessBuilder(cmd);
636 Process cmd = proc.start();
637 [...]
638 }
639}
640----
641
642And the call:
643
644----
645$ ssh -p 29418 review.example.com shell ls
646$ ssh -p 29418 review.example.com shell ps
647----
648
David Ostrovsky79c4d892014-03-15 13:52:46 +0100649[[root-level-commands]]
650=== Root Level Commands
651
David Ostrovskyb7d97752013-11-09 05:23:26 +0100652Single command plugins are also supported. In this scenario plugin binds
653SSH command to its own name. `SshModule` must inherit from
654`SingleCommandPluginModule` class:
655
656[source,java]
657----
658public class SshModule extends SingleCommandPluginModule {
659 @Override
660 protected void configure(LinkedBindingBuilder<Command> b) {
661 b.to(ShellCommand.class);
662 }
663}
664----
665
666If the plugin above is deployed under sh.jar file in `$site/plugins`
David Pursehouse659860f2013-12-16 14:50:04 +0900667directory, generic commands can be called without specifying the
David Ostrovskyb7d97752013-11-09 05:23:26 +0100668actual SSH command. Note in the example below, that the called commands
669`ls` and `ps` was not explicitly bound:
670
671----
672$ ssh -p 29418 review.example.com sh ls
673$ ssh -p 29418 review.example.com sh ps
674----
675
Martin Fick5f6222912015-11-12 14:52:50 -0700676[[search_operators]]
Edwin Kempin4b479772016-11-14 14:34:33 -0800677== Search Operators
Martin Fick5f6222912015-11-12 14:52:50 -0700678
679Plugins can define new search operators to extend change searching by
680implementing the `ChangeQueryBuilder.ChangeOperatorFactory` interface
681and registering it to an operator name in the plugin module's
682`configure()` method. The search operator name is defined during
683registration via the DynamicMap annotation mechanism. The plugin
684name will get appended to the annotated name, with an underscore
685in between, leading to the final operator name. An example
686registration looks like this:
687
688 bind(ChangeOperatorFactory.class)
689 .annotatedWith(Exports.named("sample"))
690 .to(SampleOperator.class);
691
692If this is registered in the `myplugin` plugin, then the resulting
693operator will be named `sample_myplugin`.
694
695The search operator itself is implemented by ensuring that the
696`create()` method of the class implementing the
697`ChangeQueryBuilder.ChangeOperatorFactory` interface returns a
698`Predicate<ChangeData>`. Here is a sample operator factory
David Pursehousea61ee502016-09-06 16:27:09 +0900699definition which creates a `MyPredicate`:
Martin Fick5f6222912015-11-12 14:52:50 -0700700
701[source,java]
702----
703@Singleton
704public class SampleOperator
705 implements ChangeQueryBuilder.ChangeOperatorFactory {
Edwin Kempincc82b242016-06-28 10:00:53 +0200706 public static class MyPredicate extends OperatorChangePredicate<ChangeData> {
Martin Fick5f6222912015-11-12 14:52:50 -0700707 ...
708 }
709
710 @Override
711 public Predicate<ChangeData> create(ChangeQueryBuilder builder, String value)
712 throws QueryParseException {
713 return new MyPredicate(value);
714 }
715}
716----
717
Craig Chapeldba4e892016-11-14 09:25:17 -0700718[[search_operands]]
719=== Search Operands ===
720
721Plugins can define new search operands to extend change searching.
722Plugin methods implementing search operands (returning a
723`Predicate<ChangeData>`), must be defined on a class implementing
724one of the `ChangeQueryBuilder.ChangeOperandsFactory` interfaces
725(.e.g., ChangeQueryBuilder.ChangeHasOperandFactory). The specific
726`ChangeOperandFactory` class must also be bound to the `DynamicSet` from
727a module's `configure()` method in the plugin.
728
729The new operand, when used in a search would appear as:
730 operatorName:operandName_pluginName
731
732A sample `ChangeHasOperandFactory` class implementing, and registering, a
733new `has:sample_pluginName` operand is shown below:
734
735====
736 @Singleton
737 public class SampleHasOperand implements ChangeHasOperandFactory {
738 public static class Module extends AbstractModule {
739 @Override
740 protected void configure() {
741 bind(ChangeHasOperandFactory.class)
742 .annotatedWith(Exports.named("sample")
743 .to(SampleHasOperand.class);
744 }
745 }
746
747 @Override
748 public Predicate<ChangeData> create(ChangeQueryBuilder builder)
749 throws QueryParseException {
750 return new HasSamplePredicate();
751 }
752====
753
Zac Livingston4f083a82016-05-20 12:38:43 -0600754[[command_options]]
755=== Command Options ===
756
757Plugins can provide additional options for each of the gerrit ssh and the
758REST API commands by implementing the DynamicBean interface and registering
759it to a command class name in the plugin module's `configure()` method. The
760plugin's name will be prepended to the name of each @Option annotation found
761on the DynamicBean object provided by the plugin. The example below shows a
762plugin that adds an option to log a value from the gerrit 'ban-commits'
763ssh command.
764
765[source, java]
766----
767public class SshModule extends AbstractModule {
768 private static final Logger log = LoggerFactory.getLogger(SshModule.class);
769
770 @Override
771 protected void configure() {
772 bind(DynamicOptions.DynamicBean.class)
773 .annotatedWith(Exports.named(
774 com.google.gerrit.sshd.commands.BanCommitCommand.class))
775 .to(BanOptions.class);
776 }
777
778 public static class BanOptions implements DynamicOptions.DynamicBean {
779 @Option(name = "--log", aliases = { "-l" }, usage = "Say Hello in the Log")
780 private void parse(String arg) {
781 log.error("Say Hello in the Log " + arg);
782 }
783 }
784----
Craig Chapeldba4e892016-11-14 09:25:17 -0700785
Zac Livingstoncffb24592016-11-13 09:08:08 -0700786[[query_attributes]]
787=== Query Attributes ===
788
789Plugins can provide additional attributes to be returned in Gerrit queries by
790implementing the ChangeAttributeFactory interface and registering it to the
791ChangeQueryProcessor.ChangeAttributeFactory class in the plugin module's
792'configure()' method. The new attribute(s) will be output under a "plugin"
793attribute in the change query output.
794
795The example below shows a plugin that adds two attributes ('exampleName' and
796'changeValue'), to the change query output.
797
798[source, java]
799----
800public class Module extends AbstractModule {
801 @Override
802 protected void configure() {
803 bind(ChangeAttributeFactory.class)
804 .annotatedWith(Exports.named("example"))
805 .to(AttributeFactory.class);
806 }
807}
808
809public class AttributeFactory implements ChangeAttributeFactory {
810
811 public class PluginAttribute extends PluginDefinedInfo {
812 public String exampleName;
813 public String changeValue;
814
815 public PluginAttribute(ChangeData c) {
816 this.exampleName = "Attribute Example";
817 this.changeValue = Integer.toString(c.getId().get());
818 }
819 }
820
821 @Override
822 public PluginDefinedInfo create(ChangeData c, ChangeQueryProcessor qp, String plugin) {
823 return new PluginAttribute(c);
824 }
825}
826----
827
828Example
829----
830
831ssh -p 29418 localhost gerrit query "change:1" --format json
832
833Output:
834
835{
836 "url" : "http://localhost:8080/1",
837 "plugins" : [
838 {
839 "name" : "myplugin-name",
840 "exampleName" : "Attribute Example",
841 "changeValue" : "1"
842 }
843 ],
844 ...
845}
846----
847
Edwin Kempin78ca0942013-10-30 11:24:06 +0100848[[simple-configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800849== Simple Configuration in `gerrit.config`
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200850
851In Gerrit, global configuration is stored in the `gerrit.config` file.
852If a plugin needs global configuration, this configuration should be
853stored in a `plugin` subsection in the `gerrit.config` file.
854
Edwin Kempinc9b68602013-10-30 09:32:43 +0100855This approach of storing the plugin configuration is only suitable for
856plugins that have a simple configuration that only consists of
857key-value pairs. With this approach it is not possible to have
858subsections in the plugin configuration. Plugins that require a complex
Edwin Kempin78ca0942013-10-30 11:24:06 +0100859configuration need to store their configuration in their
860link:#configuration[own configuration file] where they can make use of
861subsections. On the other hand storing the plugin configuration in a
862'plugin' subsection in the `gerrit.config` file has the advantage that
863administrators have all configuration parameters in one file, instead
864of having one configuration file per plugin.
Edwin Kempinc9b68602013-10-30 09:32:43 +0100865
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200866To avoid conflicts with other plugins, it is recommended that plugins
867only use the `plugin` subsection with their own name. For example the
868`helloworld` plugin should store its configuration in the
869`plugin.helloworld` subsection:
870
871----
872[plugin "helloworld"]
873 language = Latin
874----
875
Sasa Zivkovacdf5332013-09-20 14:05:15 +0200876Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200877plugin can easily access its configuration and there is no need for a
878plugin to parse the `gerrit.config` file on its own:
879
880[source,java]
881----
David Pursehouse529ec252013-09-27 13:45:14 +0900882@Inject
883private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200884
David Pursehoused128c892013-10-22 21:52:21 +0900885[...]
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200886
Edwin Kempin122622d2013-10-29 16:45:44 +0100887String language = cfg.getFromGerritConfig("helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +0900888 .getString("language", "English");
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200889----
890
Edwin Kempin78ca0942013-10-30 11:24:06 +0100891[[configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800892== Configuration in own config file
Edwin Kempin78ca0942013-10-30 11:24:06 +0100893
894Plugins can store their configuration in an own configuration file.
895This makes sense if the plugin configuration is rather complex and
896requires the usage of subsections. Plugins that have a simple
897key-value pair configuration can store their configuration in a
898link:#simple-configuration[`plugin` subsection of the `gerrit.config`
899file].
900
901The plugin configuration file must be named after the plugin and must
902be located in the `etc` folder of the review site. For example a
903configuration file for a `default-reviewer` plugin could look like
904this:
905
906.$site_path/etc/default-reviewer.config
907----
908[branch "refs/heads/master"]
909 reviewer = Project Owners
910 reviewer = john.doe@example.com
911[match "file:^.*\.txt"]
912 reviewer = My Info Developers
913----
914
David Pursehouse5b47bc42016-07-22 11:00:25 +0900915Plugins that have sensitive configuration settings can store those settings in
916an own secure configuration file. The plugin's secure configuration file must be
917named after the plugin and must be located in the `etc` folder of the review
918site. For example a secure configuration file for a `default-reviewer` plugin
919could look like this:
920
921.$site_path/etc/default-reviewer.secure.config
922----
923[auth]
924 password = secret
925----
926
Edwin Kempin78ca0942013-10-30 11:24:06 +0100927Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
928plugin can easily access its configuration:
929
930[source,java]
931----
932@Inject
933private com.google.gerrit.server.config.PluginConfigFactory cfg;
934
935[...]
936
937String[] reviewers = cfg.getGlobalPluginConfig("default-reviewer")
938 .getStringList("branch", "refs/heads/master", "reviewer");
David Pursehouse5b47bc42016-07-22 11:00:25 +0900939String password = cfg.getGlobalPluginConfig("default-reviewer")
940 .getString("auth", null, "password");
Edwin Kempin78ca0942013-10-30 11:24:06 +0100941----
942
Edwin Kempin78ca0942013-10-30 11:24:06 +0100943
Edwin Kempin705f2842013-10-30 14:25:31 +0100944[[simple-project-specific-configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -0800945== Simple Project Specific Configuration in `project.config`
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200946
947In Gerrit, project specific configuration is stored in the project's
948`project.config` file on the `refs/meta/config` branch. If a plugin
949needs configuration on project level (e.g. to enable its functionality
950only for certain projects), this configuration should be stored in a
951`plugin` subsection in the project's `project.config` file.
952
Edwin Kempinc9b68602013-10-30 09:32:43 +0100953This approach of storing the plugin configuration is only suitable for
954plugins that have a simple configuration that only consists of
955key-value pairs. With this approach it is not possible to have
956subsections in the plugin configuration. Plugins that require a complex
Edwin Kempin705f2842013-10-30 14:25:31 +0100957configuration need to store their configuration in their
958link:#project-specific-configuration[own configuration file] where they
959can make use of subsections. On the other hand storing the plugin
960configuration in a 'plugin' subsection in the `project.config` file has
961the advantage that project owners have all configuration parameters in
962one file, instead of having one configuration file per plugin.
Edwin Kempinc9b68602013-10-30 09:32:43 +0100963
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200964To avoid conflicts with other plugins, it is recommended that plugins
965only use the `plugin` subsection with their own name. For example the
966`helloworld` plugin should store its configuration in the
967`plugin.helloworld` subsection:
968
969----
970 [plugin "helloworld"]
971 enabled = true
972----
973
974Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
975plugin can easily access its project specific configuration and there
976is no need for a plugin to parse the `project.config` file on its own:
977
978[source,java]
979----
David Pursehouse529ec252013-09-27 13:45:14 +0900980@Inject
981private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200982
David Pursehoused128c892013-10-22 21:52:21 +0900983[...]
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200984
Edwin Kempin122622d2013-10-29 16:45:44 +0100985boolean enabled = cfg.getFromProjectConfig(project, "helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +0900986 .getBoolean("enabled", false);
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200987----
988
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200989It is also possible to get missing configuration parameters inherited
990from the parent projects:
991
992[source,java]
993----
David Pursehouse529ec252013-09-27 13:45:14 +0900994@Inject
995private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200996
David Pursehoused128c892013-10-22 21:52:21 +0900997[...]
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200998
Edwin Kempin122622d2013-10-29 16:45:44 +0100999boolean enabled = cfg.getFromProjectConfigWithInheritance(project, "helloworld")
David Pursehouse529ec252013-09-27 13:45:14 +09001000 .getBoolean("enabled", false);
Edwin Kempinca7ad8e2013-09-16 16:43:05 +02001001----
1002
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +02001003Project owners can edit the project configuration by fetching the
1004`refs/meta/config` branch, editing the `project.config` file and
1005pushing the commit back.
1006
Edwin Kempin9ce4f552013-11-15 16:00:00 +01001007Plugin configuration values that are stored in the `project.config`
1008file can be exposed in the ProjectInfoScreen to allow project owners
1009to see and edit them from the UI.
1010
1011For this an instance of `ProjectConfigEntry` needs to be bound for each
1012parameter. The export name must be a valid Git variable name. The
1013variable name is case-insensitive, allows only alphanumeric characters
1014and '-', and must start with an alphabetic character.
1015
Edwin Kempina6c1c452013-11-28 16:55:22 +01001016The example below shows how the parameters `plugin.helloworld.enabled`
1017and `plugin.helloworld.language` are bound to be editable from the
David Pursehousea1d633b2014-05-02 17:21:02 +09001018Web UI. For the parameter `plugin.helloworld.enabled` "Enable Greeting"
Edwin Kempina6c1c452013-11-28 16:55:22 +01001019is provided as display name and the default value is set to `true`.
1020For the parameter `plugin.helloworld.language` "Preferred Language"
1021is provided as display name and "en" is set as default value.
Edwin Kempin9ce4f552013-11-15 16:00:00 +01001022
1023[source,java]
1024----
1025class Module extends AbstractModule {
1026 @Override
1027 protected void configure() {
1028 bind(ProjectConfigEntry.class)
Edwin Kempina6c1c452013-11-28 16:55:22 +01001029 .annotatedWith(Exports.named("enabled"))
1030 .toInstance(new ProjectConfigEntry("Enable Greeting", true));
1031 bind(ProjectConfigEntry.class)
Edwin Kempin9ce4f552013-11-15 16:00:00 +01001032 .annotatedWith(Exports.named("language"))
1033 .toInstance(new ProjectConfigEntry("Preferred Language", "en"));
1034 }
1035}
1036----
1037
Edwin Kempinb64d3972013-11-17 18:55:48 +01001038By overwriting the `onUpdate` method of `ProjectConfigEntry` plugins
1039can be notified when this configuration parameter is updated on a
1040project.
1041
Janice Agustine5a9d012015-08-24 09:05:56 -04001042[[configuring-groups]]
1043=== Referencing groups in `project.config`
1044
1045Plugins can refer to groups so that when they are renamed, the project
1046config will also be updated in this section. The proper format to use is
Hugo Arès532e0a32017-06-16 09:31:08 -04001047the same as for any other group reference in the `project.config`, as shown below.
Janice Agustine5a9d012015-08-24 09:05:56 -04001048
1049----
Hugo Arès532e0a32017-06-16 09:31:08 -04001050group group_name
Janice Agustine5a9d012015-08-24 09:05:56 -04001051----
1052
Hugo Arès532e0a32017-06-16 09:31:08 -04001053The file `groups` must also contains the mapping of the group name and its UUID,
1054refer to link:config-project-config.html#file-groups[file groups]
1055
Edwin Kempin705f2842013-10-30 14:25:31 +01001056[[project-specific-configuration]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001057== Project Specific Configuration in own config file
Edwin Kempin705f2842013-10-30 14:25:31 +01001058
1059Plugins can store their project specific configuration in an own
1060configuration file in the projects `refs/meta/config` branch.
1061This makes sense if the plugins project specific configuration is
1062rather complex and requires the usage of subsections. Plugins that
1063have a simple key-value pair configuration can store their project
1064specific configuration in a link:#simple-project-specific-configuration[
1065`plugin` subsection of the `project.config` file].
1066
1067The plugin configuration file in the `refs/meta/config` branch must be
1068named after the plugin. For example a configuration file for a
1069`default-reviewer` plugin could look like this:
1070
1071.default-reviewer.config
1072----
1073[branch "refs/heads/master"]
1074 reviewer = Project Owners
1075 reviewer = john.doe@example.com
1076[match "file:^.*\.txt"]
1077 reviewer = My Info Developers
1078----
1079
1080Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
1081plugin can easily access its project specific configuration:
1082
1083[source,java]
1084----
1085@Inject
1086private com.google.gerrit.server.config.PluginConfigFactory cfg;
1087
1088[...]
1089
1090String[] reviewers = cfg.getProjectPluginConfig(project, "default-reviewer")
1091 .getStringList("branch", "refs/heads/master", "reviewer");
1092----
1093
Edwin Kempin762da382013-10-30 14:50:01 +01001094It is also possible to get missing configuration parameters inherited
1095from the parent projects:
1096
1097[source,java]
1098----
1099@Inject
1100private com.google.gerrit.server.config.PluginConfigFactory cfg;
1101
1102[...]
1103
David Ostrovsky468e4c32014-03-22 06:05:35 -07001104String[] reviewers = cfg.getProjectPluginConfigWithInheritance(project, "default-reviewer")
Edwin Kempin762da382013-10-30 14:50:01 +01001105 .getStringList("branch", "refs/heads/master", "reviewer");
1106----
1107
Edwin Kempin705f2842013-10-30 14:25:31 +01001108Project owners can edit the project configuration by fetching the
1109`refs/meta/config` branch, editing the `<plugin-name>.config` file and
1110pushing the commit back.
1111
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001112== React on changes in project configuration
Edwin Kempina46b6c92013-12-04 21:05:24 +01001113
1114If a plugin wants to react on changes in the project configuration, it
1115can implement a `GitReferenceUpdatedListener` and filter on events for
1116the `refs/meta/config` branch:
1117
1118[source,java]
1119----
1120public class MyListener implements GitReferenceUpdatedListener {
1121
1122 private final MetaDataUpdate.Server metaDataUpdateFactory;
1123
1124 @Inject
1125 MyListener(MetaDataUpdate.Server metaDataUpdateFactory) {
1126 this.metaDataUpdateFactory = metaDataUpdateFactory;
1127 }
1128
1129 @Override
1130 public void onGitReferenceUpdated(Event event) {
Edwin Kempina951ba52014-01-03 14:07:28 +01001131 if (event.getRefName().equals(RefNames.REFS_CONFIG)) {
Edwin Kempina46b6c92013-12-04 21:05:24 +01001132 Project.NameKey p = new Project.NameKey(event.getProjectName());
1133 try {
Edwin Kempina951ba52014-01-03 14:07:28 +01001134 ProjectConfig oldCfg = parseConfig(p, event.getOldObjectId());
1135 ProjectConfig newCfg = parseConfig(p, event.getNewObjectId());
Edwin Kempina46b6c92013-12-04 21:05:24 +01001136
Edwin Kempina951ba52014-01-03 14:07:28 +01001137 if (oldCfg != null && newCfg != null
1138 && !oldCfg.getProject().getSubmitType().equals(newCfg.getProject().getSubmitType())) {
Edwin Kempina46b6c92013-12-04 21:05:24 +01001139 // submit type has changed
1140 ...
1141 }
1142 } catch (IOException | ConfigInvalidException e) {
1143 ...
1144 }
1145 }
1146 }
Edwin Kempina951ba52014-01-03 14:07:28 +01001147
1148 private ProjectConfig parseConfig(Project.NameKey p, String idStr)
1149 throws IOException, ConfigInvalidException, RepositoryNotFoundException {
1150 ObjectId id = ObjectId.fromString(idStr);
1151 if (ObjectId.zeroId().equals(id)) {
1152 return null;
1153 }
1154 return ProjectConfig.read(metaDataUpdateFactory.create(p), id);
1155 }
Edwin Kempina46b6c92013-12-04 21:05:24 +01001156}
1157----
1158
1159
David Ostrovsky7066cc02013-06-15 14:46:23 +02001160[[capabilities]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001161== Plugin Owned Capabilities
David Ostrovsky7066cc02013-06-15 14:46:23 +02001162
1163Plugins may provide their own capabilities and restrict usage of SSH
Dariusz Luksza112d93a2014-06-01 16:52:23 +02001164commands or `UiAction` to the users who are granted those capabilities.
David Ostrovsky7066cc02013-06-15 14:46:23 +02001165
1166Plugins define the capabilities by overriding the `CapabilityDefinition`
1167abstract class:
1168
David Pursehouse68153d72013-09-04 10:09:17 +09001169[source,java]
1170----
1171public class PrintHelloCapability extends CapabilityDefinition {
1172 @Override
1173 public String getDescription() {
1174 return "Print Hello";
David Ostrovsky7066cc02013-06-15 14:46:23 +02001175 }
David Pursehouse68153d72013-09-04 10:09:17 +09001176}
1177----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001178
Dariusz Luksza112d93a2014-06-01 16:52:23 +02001179If no Guice modules are declared in the manifest, capability may
David Ostrovsky7066cc02013-06-15 14:46:23 +02001180use auto-registration by providing an `@Export` annotation:
1181
David Pursehouse68153d72013-09-04 10:09:17 +09001182[source,java]
1183----
1184@Export("printHello")
1185public class PrintHelloCapability extends CapabilityDefinition {
David Pursehoused128c892013-10-22 21:52:21 +09001186 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001187}
1188----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001189
1190Otherwise the capability must be bound in a plugin module:
1191
David Pursehouse68153d72013-09-04 10:09:17 +09001192[source,java]
1193----
1194public class HelloWorldModule extends AbstractModule {
1195 @Override
1196 protected void configure() {
1197 bind(CapabilityDefinition.class)
1198 .annotatedWith(Exports.named("printHello"))
1199 .to(PrintHelloCapability.class);
David Ostrovsky7066cc02013-06-15 14:46:23 +02001200 }
David Pursehouse68153d72013-09-04 10:09:17 +09001201}
1202----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001203
1204With a plugin-owned capability defined in this way, it is possible to restrict
David Ostrovskyf86bae52013-09-01 09:10:39 +02001205usage of an SSH command or `UiAction` to members of the group that were granted
David Ostrovsky7066cc02013-06-15 14:46:23 +02001206this capability in the usual way, using the `RequiresCapability` annotation:
1207
David Pursehouse68153d72013-09-04 10:09:17 +09001208[source,java]
1209----
1210@RequiresCapability("printHello")
1211@CommandMetaData(name="print", description="Print greeting in different languages")
1212public final class PrintHelloWorldCommand extends SshCommand {
David Pursehoused128c892013-10-22 21:52:21 +09001213 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001214}
1215----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001216
David Ostrovskyf86bae52013-09-01 09:10:39 +02001217Or with `UiAction`:
David Ostrovsky7066cc02013-06-15 14:46:23 +02001218
David Pursehouse68153d72013-09-04 10:09:17 +09001219[source,java]
1220----
1221@RequiresCapability("printHello")
1222public class SayHelloAction extends UiAction<RevisionResource>
1223 implements RestModifyView<RevisionResource, SayHelloAction.Input> {
David Pursehoused128c892013-10-22 21:52:21 +09001224 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001225}
1226----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001227
1228Capability scope was introduced to differentiate between plugin-owned
David Pursehousebf053342013-09-05 14:55:29 +09001229capabilities and core capabilities. Per default the scope of the
1230`@RequiresCapability` annotation is `CapabilityScope.CONTEXT`, that means:
1231
David Ostrovsky7066cc02013-06-15 14:46:23 +02001232* when `@RequiresCapability` is used within a plugin the scope of the
1233capability is assumed to be that plugin.
David Pursehousebf053342013-09-05 14:55:29 +09001234
David Ostrovsky7066cc02013-06-15 14:46:23 +02001235* If `@RequiresCapability` is used within the core Gerrit Code Review server
1236(and thus is outside of a plugin) the scope is the core server and will use
1237the `GlobalCapability` known to Gerrit Code Review server.
1238
1239If a plugin needs to use a core capability name (e.g. "administrateServer")
1240this can be specified by setting `scope = CapabilityScope.CORE`:
1241
David Pursehouse68153d72013-09-04 10:09:17 +09001242[source,java]
1243----
1244@RequiresCapability(value = "administrateServer", scope =
1245 CapabilityScope.CORE)
David Pursehoused128c892013-10-22 21:52:21 +09001246 [...]
David Pursehouse68153d72013-09-04 10:09:17 +09001247----
David Ostrovsky7066cc02013-06-15 14:46:23 +02001248
David Ostrovskyf86bae52013-09-01 09:10:39 +02001249[[ui_extension]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001250== UI Extension
David Ostrovskyf86bae52013-09-01 09:10:39 +02001251
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001252[[panels]]
1253=== Panels
1254
1255GWT plugins can contribute panels to Gerrit screens.
1256
1257Gerrit screens define extension points where plugins can add GWT
1258panels with custom controls:
1259
1260* Change Screen:
Edwin Kempin2a8c5152015-07-08 14:28:57 +02001261** `GerritUiExtensionPoint.CHANGE_SCREEN_HEADER`:
1262+
1263Panel will be shown in the header bar to the right of the change
1264status.
1265
Edwin Kempin745021e2015-07-09 13:09:44 +02001266** `GerritUiExtensionPoint.CHANGE_SCREEN_HEADER_RIGHT_OF_BUTTONS`:
1267+
1268Panel will be shown in the header bar on the right side of the buttons.
1269
Edwin Kempincbc95252015-07-09 11:37:53 +02001270** `GerritUiExtensionPoint.CHANGE_SCREEN_HEADER_RIGHT_OF_POP_DOWNS`:
1271+
1272Panel will be shown in the header bar on the right side of the pop down
1273buttons.
1274
Khai Do675afc02016-07-28 16:30:37 -07001275** `GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_COMMIT_INFO_BLOCK`:
1276+
1277Panel will be shown below the commit info block.
1278
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001279** `GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK`:
1280+
1281Panel will be shown below the change info block.
1282
Khai Do76c830c2016-07-28 16:35:45 -07001283** `GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_RELATED_INFO_BLOCK`:
1284+
1285Panel will be shown below the related info block.
1286
Khai Do83940ba2016-09-20 15:15:45 +02001287** `GerritUiExtensionPoint.CHANGE_SCREEN_HISTORY_RIGHT_OF_BUTTONS`:
1288+
1289Panel will be shown in the history bar on the right side of the buttons.
1290
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001291** The following parameters are provided:
Edwin Kempin5d683cc2015-07-10 15:47:14 +02001292*** `GerritUiExtensionPoint.Key.CHANGE_INFO`:
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001293+
Edwin Kempin5d683cc2015-07-10 15:47:14 +02001294The link:rest-api-changes.html#change-info[ChangeInfo] entity for the
1295current change.
David Ostrovsky916ae0c2016-03-15 17:05:41 +01001296+
1297The link:rest-api-changes.html#revision-info[RevisionInfo] entity for
1298the current patch set.
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001299
Edwin Kempin88b947a2015-07-08 09:03:56 +02001300* Project Info Screen:
1301** `GerritUiExtensionPoint.PROJECT_INFO_SCREEN_TOP`:
1302+
1303Panel will be shown at the top of the screen.
1304
1305** `GerritUiExtensionPoint.PROJECT_INFO_SCREEN_BOTTOM`:
1306+
1307Panel will be shown at the bottom of the screen.
1308
1309** The following parameters are provided:
1310*** `GerritUiExtensionPoint.Key.PROJECT_NAME`:
1311+
1312The name of the project.
1313
Edwin Kempin241d9db2015-07-08 13:53:50 +02001314* User Password Screen:
1315** `GerritUiExtensionPoint.PASSWORD_SCREEN_BOTTOM`:
1316+
1317Panel will be shown at the bottom of the screen.
1318
Edwin Kempin30c6f472015-07-09 14:27:52 +02001319** The following parameters are provided:
1320*** `GerritUiExtensionPoint.Key.ACCOUNT_INFO`:
1321+
1322The link:rest-api-accounts.html#account-info[AccountInfo] entity for
1323the current user.
1324
Edwin Kempin1cd95f92015-07-14 08:27:20 +02001325* User Preferences Screen:
1326** `GerritUiExtensionPoint.PREFERENCES_SCREEN_BOTTOM`:
1327+
1328Panel will be shown at the bottom of the screen.
1329
1330** The following parameters are provided:
1331*** `GerritUiExtensionPoint.Key.ACCOUNT_INFO`:
1332+
1333The link:rest-api-accounts.html#account-info[AccountInfo] entity for
1334the current user.
1335
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001336* User Profile Screen:
1337** `GerritUiExtensionPoint.PROFILE_SCREEN_BOTTOM`:
1338+
1339Panel will be shown at the bottom of the screen below the grid with the
1340profile data.
1341
Edwin Kempin30c6f472015-07-09 14:27:52 +02001342** The following parameters are provided:
1343*** `GerritUiExtensionPoint.Key.ACCOUNT_INFO`:
1344+
1345The link:rest-api-accounts.html#account-info[AccountInfo] entity for
1346the current user.
1347
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001348Example panel:
1349[source,java]
1350----
1351public class MyPlugin extends PluginEntryPoint {
1352 @Override
1353 public void onPluginLoad() {
1354 Plugin.get().panel(GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK,
Zac Livingstone7f3d1a2017-03-01 12:47:37 -07001355 "my_panel_name",
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001356 new Panel.EntryPoint() {
1357 @Override
1358 public void onLoad(Panel panel) {
1359 panel.setWidget(new InlineLabel("My Panel for change "
1360 + panel.getInt(GerritUiExtensionPoint.Key.CHANGE_ID, -1));
1361 }
1362 });
1363 }
1364}
1365----
1366
Zac Livingstone7f3d1a2017-03-01 12:47:37 -07001367Change Screen panel ordering may be specified in the
1368project config. Values may be either "plugin name" or
1369"plugin name"."panel name".
1370Panels not specified in the config will be added
1371to the end in load order. Panels specified in the config that
1372are not found will be ignored.
1373
1374Example config:
1375----
1376[extension-panels "CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK"]
1377 panel = helloworld.change_id
1378 panel = myotherplugin
1379 panel = myplugin.my_panel_name
1380----
1381
1382
1383
Edwin Kempin52f79ac2015-07-07 16:37:50 +02001384[[actions]]
1385=== Actions
1386
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001387Plugins can contribute UI actions on core Gerrit pages. This is useful
1388for workflow customization or exposing plugin functionality through the
1389UI in addition to SSH commands and the REST API.
David Ostrovskyf86bae52013-09-01 09:10:39 +02001390
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001391For instance a plugin to integrate Jira with Gerrit changes may
1392contribute a "File bug" button to allow filing a bug from the change
1393page or plugins to integrate continuous integration systems may
1394contribute a "Schedule" button to allow a CI build to be scheduled
1395manually from the patch set panel.
David Ostrovskyf86bae52013-09-01 09:10:39 +02001396
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001397Two different places on core Gerrit pages are supported:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001398
1399* Change screen
1400* Project info screen
1401
1402Plugins contribute UI actions by implementing the `UiAction` interface:
1403
David Pursehouse68153d72013-09-04 10:09:17 +09001404[source,java]
1405----
1406@RequiresCapability("printHello")
1407class HelloWorldAction implements UiAction<RevisionResource>,
1408 RestModifyView<RevisionResource, HelloWorldAction.Input> {
1409 static class Input {
1410 boolean french;
1411 String message;
David Ostrovskyf86bae52013-09-01 09:10:39 +02001412 }
David Pursehouse68153d72013-09-04 10:09:17 +09001413
1414 private Provider<CurrentUser> user;
1415
1416 @Inject
1417 HelloWorldAction(Provider<CurrentUser> user) {
1418 this.user = user;
1419 }
1420
1421 @Override
1422 public String apply(RevisionResource rev, Input input) {
1423 final String greeting = input.french
1424 ? "Bonjour"
1425 : "Hello";
1426 return String.format("%s %s from change %s, patch set %d!",
1427 greeting,
1428 Strings.isNullOrEmpty(input.message)
1429 ? Objects.firstNonNull(user.get().getUserName(), "world")
1430 : input.message,
1431 rev.getChange().getId().toString(),
1432 rev.getPatchSet().getPatchSetId());
1433 }
1434
1435 @Override
1436 public Description getDescription(
1437 RevisionResource resource) {
1438 return new Description()
1439 .setLabel("Say hello")
1440 .setTitle("Say hello in different languages");
1441 }
1442}
1443----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001444
David Ostrovsky450eefe2013-10-21 21:18:11 +02001445Sometimes plugins may want to be able to change the state of a patch set or
1446change in the `UiAction.apply()` method and reflect these changes on the core
1447UI. For example a buildbot plugin which exposes a 'Schedule' button on the
1448patch set panel may want to disable that button after the build was scheduled
1449and update the tooltip of that button. But because of Gerrit's caching
1450strategy the following must be taken into consideration.
1451
1452The browser is allowed to cache the `UiAction` information until something on
1453the change is modified. More accurately the change row needs to be modified in
1454the database to have a more recent `lastUpdatedOn` or a new `rowVersion`, or
1455the +refs/meta/config+ of the project or any parents needs to change to a new
1456SHA-1. The ETag SHA-1 computation code can be found in the
1457`ChangeResource.getETag()` method.
1458
David Pursehoused128c892013-10-22 21:52:21 +09001459The easiest way to accomplish this is to update `lastUpdatedOn` of the change:
David Ostrovsky450eefe2013-10-21 21:18:11 +02001460
1461[source,java]
1462----
1463@Override
1464public Object apply(RevisionResource rcrs, Input in) {
1465 // schedule a build
1466 [...]
1467 // update change
1468 ReviewDb db = dbProvider.get();
Edwin Kempine2d06b02016-02-17 18:34:17 +01001469 try (BatchUpdate bu = batchUpdateFactory.create(
1470 db, project.getNameKey(), user, TimeUtil.nowTs())) {
1471 bu.addOp(change.getId(), new BatchUpdate.Op() {
1472 @Override
Dave Borowitzb91cf222017-03-10 13:11:59 -05001473 public boolean updateChange(ChangeContext ctx) {
Edwin Kempine2d06b02016-02-17 18:34:17 +01001474 return true;
1475 }
1476 });
1477 bu.execute();
David Ostrovsky450eefe2013-10-21 21:18:11 +02001478 }
David Pursehoused128c892013-10-22 21:52:21 +09001479 [...]
David Ostrovsky450eefe2013-10-21 21:18:11 +02001480}
1481----
1482
David Ostrovskyf86bae52013-09-01 09:10:39 +02001483`UiAction` must be bound in a plugin module:
1484
David Pursehouse68153d72013-09-04 10:09:17 +09001485[source,java]
1486----
1487public class Module extends AbstractModule {
1488 @Override
1489 protected void configure() {
1490 install(new RestApiModule() {
1491 @Override
1492 protected void configure() {
1493 post(REVISION_KIND, "say-hello")
1494 .to(HelloWorldAction.class);
1495 }
1496 });
David Ostrovskyf86bae52013-09-01 09:10:39 +02001497 }
David Pursehouse68153d72013-09-04 10:09:17 +09001498}
1499----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001500
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001501The module above must be declared in the `pom.xml` for Maven driven
1502plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001503
David Pursehouse68153d72013-09-04 10:09:17 +09001504[source,xml]
1505----
1506<manifestEntries>
1507 <Gerrit-Module>com.googlesource.gerrit.plugins.cookbook.Module</Gerrit-Module>
1508</manifestEntries>
1509----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001510
David Ostrovskyfdbfcad2016-11-15 06:35:29 -08001511or in the `BUILD` configuration file for Bazel driven plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001512
David Pursehouse68153d72013-09-04 10:09:17 +09001513[source,python]
1514----
1515manifest_entries = [
1516 'Gerrit-Module: com.googlesource.gerrit.plugins.cookbook.Module',
1517]
1518----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001519
1520In some use cases more user input must be gathered, for that `UiAction` can be
1521combined with the JavaScript API. This would display a small popup near the
1522activation button to gather additional input from the user. The JS file is
1523typically put in the `static` folder within the plugin's directory:
1524
David Pursehouse68153d72013-09-04 10:09:17 +09001525[source,javascript]
1526----
1527Gerrit.install(function(self) {
1528 function onSayHello(c) {
1529 var f = c.textfield();
1530 var t = c.checkbox();
1531 var b = c.button('Say hello', {onclick: function(){
1532 c.call(
1533 {message: f.value, french: t.checked},
1534 function(r) {
1535 c.hide();
1536 window.alert(r);
1537 c.refresh();
1538 });
1539 }});
1540 c.popup(c.div(
1541 c.prependLabel('Greeting message', f),
1542 c.br(),
1543 c.label(t, 'french'),
1544 c.br(),
1545 b));
1546 f.focus();
1547 }
1548 self.onAction('revision', 'say-hello', onSayHello);
1549});
1550----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001551
1552The JS module must be exposed as a `WebUiPlugin` and bound as
1553an HTTP Module:
1554
David Pursehouse68153d72013-09-04 10:09:17 +09001555[source,java]
1556----
1557public class HttpModule extends HttpPluginModule {
1558 @Override
1559 protected void configureServlets() {
1560 DynamicSet.bind(binder(), WebUiPlugin.class)
1561 .toInstance(new JavaScriptPlugin("hello.js"));
David Ostrovskyf86bae52013-09-01 09:10:39 +02001562 }
David Pursehouse68153d72013-09-04 10:09:17 +09001563}
1564----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001565
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001566The HTTP module above must be declared in the `pom.xml` for Maven
1567driven plugins:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001568
David Pursehouse68153d72013-09-04 10:09:17 +09001569[source,xml]
1570----
1571<manifestEntries>
1572 <Gerrit-HttpModule>com.googlesource.gerrit.plugins.cookbook.HttpModule</Gerrit-HttpModule>
1573</manifestEntries>
1574----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001575
David Ostrovskyfdbfcad2016-11-15 06:35:29 -08001576or in the `BUILD` configuration file for Bazel driven plugins
David Ostrovskyf86bae52013-09-01 09:10:39 +02001577
David Pursehouse68153d72013-09-04 10:09:17 +09001578[source,python]
1579----
1580manifest_entries = [
1581 'Gerrit-HttpModule: com.googlesource.gerrit.plugins.cookbook.HttpModule',
1582]
1583----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001584
1585If `UiAction` is annotated with the `@RequiresCapability` annotation, then the
1586capability check is done during the `UiAction` gathering, so the plugin author
1587doesn't have to set `UiAction.Description.setVisible()` explicitly in this
1588case.
1589
David Pursehousea61ee502016-09-06 16:27:09 +09001590The following prerequisites must be met, to satisfy the capability check:
David Ostrovskyf86bae52013-09-01 09:10:39 +02001591
1592* user is authenticated
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001593* user is a member of a group which has the `Administrate Server` capability, or
David Ostrovskyf86bae52013-09-01 09:10:39 +02001594* user is a member of a group which has the required capability
1595
1596The `apply` method is called when the button is clicked. If `UiAction` is
1597combined with JavaScript API (its own JavaScript function is provided),
1598then a popup dialog is normally opened to gather additional user input.
1599A new button is placed on the popup dialog to actually send the request.
1600
1601Every `UiAction` exposes a REST API endpoint. The endpoint from the example above
1602can be accessed from any REST client, i. e.:
1603
Michael Ochmannb99feab2016-07-06 14:10:22 +02001604----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001605 curl -X POST -H "Content-Type: application/json" \
1606 -d '{message: "François", french: true}' \
Han-Wen Nienhuys84d830b2017-02-15 16:36:04 +01001607 --user joe:secret \
David Ostrovskyf86bae52013-09-01 09:10:39 +02001608 http://host:port/a/changes/1/revisions/1/cookbook~say-hello
1609 "Bonjour François from change 1, patch set 1!"
Michael Ochmannb99feab2016-07-06 14:10:22 +02001610----
David Ostrovskyf86bae52013-09-01 09:10:39 +02001611
David Pursehouse42245822013-09-24 09:48:20 +09001612A special case is to bind an endpoint without a view name. This is
Edwin Kempin7afa73c2013-11-08 07:48:47 +01001613particularly useful for `DELETE` requests:
David Ostrovskyc6d19ed2013-09-20 21:30:18 +02001614
1615[source,java]
1616----
1617public class Module extends AbstractModule {
1618 @Override
1619 protected void configure() {
1620 install(new RestApiModule() {
1621 @Override
1622 protected void configure() {
1623 delete(PROJECT_KIND)
1624 .to(DeleteProject.class);
1625 }
1626 });
1627 }
1628}
1629----
1630
David Pursehouse42245822013-09-24 09:48:20 +09001631For a `UiAction` bound this way, a JS API function can be provided.
1632
1633Currently only one restriction exists: per plugin only one `UiAction`
David Ostrovskyc6d19ed2013-09-20 21:30:18 +02001634can be bound per resource without view name. To define a JS function
1635for the `UiAction`, "/" must be used as the name:
1636
1637[source,javascript]
1638----
1639Gerrit.install(function(self) {
1640 function onDeleteProject(c) {
1641 [...]
1642 }
1643 self.onAction('project', '/', onDeleteProject);
1644});
1645----
1646
Dave Borowitzf1790ce2016-11-14 12:26:12 -08001647
1648[[action-visitor]]
1649=== Action Visitors
1650
1651In addition to providing new actions, plugins can have fine-grained control
1652over the link:rest-api-changes.html#action-info[ActionInfo] map, modifying or
1653removing existing actions, including those contributed by core.
1654
1655Visitors are provided the link:rest-api-changes.html#action-info[ActionInfo],
1656which is mutable, along with copies of the
1657link:rest-api-changes.html#change-info[ChangeInfo] and
1658link:rest-api-changes.html#revision-info[RevisionInfo]. They can modify the
1659action, or return `false` to exclude it from the resulting map.
1660
1661These operations only affect the action buttons that are displayed in the UI;
1662the underlying REST API endpoints are not affected. Multiple plugins may
1663implement the visitor interface, but the order in which they are run is
1664undefined.
1665
1666For example, to exclude "Cherry-Pick" only from certain projects, and rename
1667"Abandon":
1668
1669[source,java]
1670----
1671public class MyActionVisitor implements ActionVisitor {
1672 @Override
1673 public boolean visit(String name, ActionInfo actionInfo,
1674 ChangeInfo changeInfo) {
1675 if (name.equals("abandon")) {
1676 actionInfo.label = "Drop";
1677 }
1678 return true;
1679 }
1680
1681 @Override
1682 public boolean visit(String name, ActionInfo actionInfo,
1683 ChangeInfo changeInfo, RevisionInfo revisionInfo) {
1684 if (project.startsWith("some-team/") && name.equals("cherrypick")) {
1685 return false;
1686 }
1687 return true;
1688 }
1689}
1690----
1691
1692
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001693[[top-menu-extensions]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001694== Top Menu Extensions
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001695
1696Plugins can contribute items to Gerrit's top menu.
1697
1698A single top menu extension can have multiple elements and will be put as
1699the last element in Gerrit's top menu.
1700
1701Plugins define the top menu entries by implementing `TopMenu` interface:
1702
1703[source,java]
1704----
1705public class MyTopMenuExtension implements TopMenu {
1706
1707 @Override
1708 public List<MenuEntry> getEntries() {
1709 return Lists.newArrayList(
1710 new MenuEntry("Top Menu Entry", Lists.newArrayList(
1711 new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
1712 }
1713}
1714----
1715
Edwin Kempin77f23242013-09-30 14:53:20 +02001716Plugins can also add additional menu items to Gerrit's top menu entries
1717by defining a `MenuEntry` that has the same name as a Gerrit top menu
1718entry:
1719
1720[source,java]
1721----
1722public class MyTopMenuExtension implements TopMenu {
1723
1724 @Override
1725 public List<MenuEntry> getEntries() {
1726 return Lists.newArrayList(
Dariusz Luksza2d3afab2013-10-01 11:07:13 +02001727 new MenuEntry(GerritTopMenu.PROJECTS, Lists.newArrayList(
Edwin Kempin77f23242013-09-30 14:53:20 +02001728 new MenuItem("Browse Repositories", "https://gerrit.googlesource.com/"))));
1729 }
1730}
1731----
1732
Dariusz Lukszae8de74f2014-06-04 19:39:20 +02001733`MenuItems` that are bound for the `MenuEntry` with the name
1734`GerritTopMenu.PROJECTS` can contain a `${projectName}` placeholder
1735which is automatically replaced by the actual project name.
1736
1737E.g. plugins may register an link:#http[HTTP Servlet] to handle project
1738specific requests and add an menu item for this:
1739
1740[source,java]
1741---
1742 new MenuItem("My Screen", "/plugins/myplugin/project/${projectName}");
1743---
1744
1745This also enables plugins to provide menu items for project aware
1746screens:
1747
1748[source,java]
1749---
1750 new MenuItem("My Screen", "/x/my-screen/for/${projectName}");
1751---
1752
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001753If no Guice modules are declared in the manifest, the top menu extension may use
1754auto-registration by providing an `@Listen` annotation:
1755
1756[source,java]
1757----
1758@Listen
1759public class MyTopMenuExtension implements TopMenu {
David Pursehoused128c892013-10-22 21:52:21 +09001760 [...]
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001761}
1762----
1763
Luca Milanesiocb230402013-10-11 08:49:56 +01001764Otherwise the top menu extension must be bound in the plugin module used
1765for the Gerrit system injector (Gerrit-Module entry in MANIFEST.MF):
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001766
1767[source,java]
1768----
Luca Milanesiocb230402013-10-11 08:49:56 +01001769package com.googlesource.gerrit.plugins.helloworld;
1770
Dariusz Luksza589ba00aa2013-05-07 17:21:23 +02001771public class HelloWorldModule extends AbstractModule {
1772 @Override
1773 protected void configure() {
1774 DynamicSet.bind(binder(), TopMenu.class).to(MyTopMenuExtension.class);
1775 }
1776}
1777----
1778
Luca Milanesiocb230402013-10-11 08:49:56 +01001779[source,manifest]
1780----
1781Gerrit-ApiType: plugin
1782Gerrit-Module: com.googlesource.gerrit.plugins.helloworld.HelloWorldModule
1783----
1784
Edwin Kempinb2e926a2013-11-11 16:38:30 +01001785It is also possible to show some menu entries only if the user has a
1786certain capability:
1787
1788[source,java]
1789----
1790public class MyTopMenuExtension implements TopMenu {
1791 private final String pluginName;
1792 private final Provider<CurrentUser> userProvider;
1793 private final List<MenuEntry> menuEntries;
1794
1795 @Inject
1796 public MyTopMenuExtension(@PluginName String pluginName,
1797 Provider<CurrentUser> userProvider) {
1798 this.pluginName = pluginName;
1799 this.userProvider = userProvider;
1800 menuEntries = new ArrayList<TopMenu.MenuEntry>();
1801
1802 // add menu entry that is only visible to users with a certain capability
1803 if (canSeeMenuEntry()) {
1804 menuEntries.add(new MenuEntry("Top Menu Entry", Collections
1805 .singletonList(new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
1806 }
1807
1808 // add menu entry that is visible to all users (even anonymous users)
1809 menuEntries.add(new MenuEntry("Top Menu Entry", Collections
1810 .singletonList(new MenuItem("Documentation", "/plugins/myplugin/"))));
1811 }
1812
1813 private boolean canSeeMenuEntry() {
1814 if (userProvider.get().isIdentifiedUser()) {
1815 CapabilityControl ctl = userProvider.get().getCapabilities();
1816 return ctl.canPerform(pluginName + "-" + MyCapability.ID)
1817 || ctl.canAdministrateServer();
1818 } else {
1819 return false;
1820 }
1821 }
1822
1823 @Override
1824 public List<MenuEntry> getEntries() {
1825 return menuEntries;
1826 }
1827}
1828----
1829
Dave Borowitzf1790ce2016-11-14 12:26:12 -08001830
Edwin Kempin3c024ea2013-11-11 10:43:46 +01001831[[gwt_ui_extension]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08001832== GWT UI Extension
Edwin Kempin3c024ea2013-11-11 10:43:46 +01001833Plugins can extend the Gerrit UI with own GWT code.
1834
Edwin Kempinb74daa92013-11-11 11:28:16 +01001835A GWT plugin must contain a GWT module file, e.g. `HelloPlugin.gwt.xml`,
1836that bundles together all the configuration settings of the GWT plugin:
1837
1838[source,xml]
1839----
1840<?xml version="1.0" encoding="UTF-8"?>
1841<module rename-to="hello_gwt_plugin">
1842 <!-- Inherit the core Web Toolkit stuff. -->
1843 <inherits name="com.google.gwt.user.User"/>
1844 <!-- Other module inherits -->
1845 <inherits name="com.google.gerrit.Plugin"/>
1846 <inherits name="com.google.gwt.http.HTTP"/>
1847 <!-- Using GWT built-in themes adds a number of static -->
1848 <!-- resources to the plugin. No theme inherits lines were -->
1849 <!-- added in order to make this plugin as simple as possible -->
1850 <!-- Specify the app entry point class. -->
1851 <entry-point class="${package}.client.HelloPlugin"/>
1852 <stylesheet src="hello.css"/>
1853</module>
1854----
1855
1856The GWT module must inherit `com.google.gerrit.Plugin` and
1857`com.google.gwt.http.HTTP`.
1858
1859To register the GWT module a `GwtPlugin` needs to be bound.
1860
1861If no Guice modules are declared in the manifest, the GWT plugin may
1862use auto-registration by using the `@Listen` annotation:
1863
1864[source,java]
1865----
1866@Listen
1867public class MyExtension extends GwtPlugin {
1868 public MyExtension() {
1869 super("hello_gwt_plugin");
1870 }
1871}
1872----
1873
1874Otherwise the binding must be done in an `HttpModule`:
1875
1876[source,java]
1877----
1878public class HttpModule extends HttpPluginModule {
1879
1880 @Override
1881 protected void configureServlets() {
1882 DynamicSet.bind(binder(), WebUiPlugin.class)
1883 .toInstance(new GwtPlugin("hello_gwt_plugin"));
1884 }
1885}
1886----
1887
1888The HTTP module above must be declared in the `pom.xml` for Maven
1889driven plugins:
1890
1891[source,xml]
1892----
1893<manifestEntries>
1894 <Gerrit-HttpModule>com.googlesource.gerrit.plugins.myplugin.HttpModule</Gerrit-HttpModule>
1895</manifestEntries>
1896----
1897
Shawn Pearcec8e96ad2013-12-09 08:20:44 -08001898The name that is provided to the `GwtPlugin` must match the GWT
1899module name compiled into the plugin. The name of the GWT module
1900can be explicitly set in the GWT module XML file by specifying
1901the `rename-to` attribute on the module. It is important that the
1902module name be unique across all plugins installed on the server,
1903as the module name determines the JavaScript namespace used by the
1904compiled plugin code.
Edwin Kempinb74daa92013-11-11 11:28:16 +01001905
1906[source,xml]
1907----
1908<module rename-to="hello_gwt_plugin">
1909----
1910
1911The actual GWT code must be implemented in a class that extends
Shawn Pearcec8e96ad2013-12-09 08:20:44 -08001912`com.google.gerrit.plugin.client.PluginEntryPoint`:
Edwin Kempinb74daa92013-11-11 11:28:16 +01001913
1914[source,java]
1915----
Shawn Pearcec8e96ad2013-12-09 08:20:44 -08001916public class HelloPlugin extends PluginEntryPoint {
Edwin Kempinb74daa92013-11-11 11:28:16 +01001917
1918 @Override
Shawn Pearcec8e96ad2013-12-09 08:20:44 -08001919 public void onPluginLoad() {
Edwin Kempinb74daa92013-11-11 11:28:16 +01001920 // Create the dialog box
1921 final DialogBox dialogBox = new DialogBox();
1922
1923 // The content of the dialog comes from a User specified Preference
1924 dialogBox.setText("Hello from GWT Gerrit UI plugin");
1925 dialogBox.setAnimationEnabled(true);
1926 Button closeButton = new Button("Close");
1927 VerticalPanel dialogVPanel = new VerticalPanel();
1928 dialogVPanel.setWidth("100%");
1929 dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
1930 dialogVPanel.add(closeButton);
1931
1932 closeButton.addClickHandler(new ClickHandler() {
1933 public void onClick(ClickEvent event) {
1934 dialogBox.hide();
1935 }
1936 });
1937
1938 // Set the contents of the Widget
1939 dialogBox.setWidget(dialogVPanel);
1940
1941 RootPanel rootPanel = RootPanel.get(HelloMenu.MENU_ID);
1942 rootPanel.getElement().removeAttribute("href");
1943 rootPanel.addDomHandler(new ClickHandler() {
1944 @Override
1945 public void onClick(ClickEvent event) {
1946 dialogBox.center();
1947 dialogBox.show();
1948 }
1949 }, ClickEvent.getType());
1950 }
1951}
1952----
1953
1954This class must be set as entry point in the GWT module:
1955
1956[source,xml]
1957----
1958<entry-point class="${package}.client.HelloPlugin"/>
1959----
1960
1961In addition this class must be defined as module in the `pom.xml` for the
1962`gwt-maven-plugin` and the `webappDirectory` option of `gwt-maven-plugin`
1963must be set to `${project.build.directory}/classes/static`:
1964
1965[source,xml]
1966----
1967<plugin>
1968 <groupId>org.codehaus.mojo</groupId>
1969 <artifactId>gwt-maven-plugin</artifactId>
David Pursehouse7ab81732015-05-07 12:00:47 +09001970 <version>2.7.0</version>
Edwin Kempinb74daa92013-11-11 11:28:16 +01001971 <configuration>
1972 <module>com.googlesource.gerrit.plugins.myplugin.HelloPlugin</module>
1973 <disableClassMetadata>true</disableClassMetadata>
1974 <disableCastChecking>true</disableCastChecking>
1975 <webappDirectory>${project.build.directory}/classes/static</webappDirectory>
1976 </configuration>
1977 <executions>
1978 <execution>
1979 <goals>
1980 <goal>compile</goal>
1981 </goals>
1982 </execution>
1983 </executions>
1984</plugin>
1985----
1986
1987To attach a GWT widget defined by the plugin to the Gerrit core UI
1988`com.google.gwt.user.client.ui.RootPanel` can be used to manipulate the
1989Gerrit core widgets:
1990
1991[source,java]
1992----
1993RootPanel rootPanel = RootPanel.get(HelloMenu.MENU_ID);
1994rootPanel.getElement().removeAttribute("href");
1995rootPanel.addDomHandler(new ClickHandler() {
1996 @Override
1997 public void onClick(ClickEvent event) {
1998 dialogBox.center();
1999 dialogBox.show();
2000 }
2001}, ClickEvent.getType());
2002----
2003
2004GWT plugins can come with their own css file. This css file must have a
2005unique name and must be registered in the GWT module:
2006
2007[source,xml]
2008----
2009<stylesheet src="hello.css"/>
2010----
2011
Edwin Kempin2570b102013-11-11 11:44:50 +01002012If a GWT plugin wants to invoke the Gerrit REST API it can use
David Pursehouse3a388312014-02-25 16:41:47 +09002013`com.google.gerrit.plugin.client.rpc.RestApi` to construct the URL
Edwin Kempin2570b102013-11-11 11:44:50 +01002014path and to trigger the REST calls.
2015
2016Example for invoking a Gerrit core REST endpoint:
2017
2018[source,java]
2019----
2020new RestApi("projects").id(projectName).view("description")
2021 .put("new description", new AsyncCallback<JavaScriptObject>() {
2022
2023 @Override
2024 public void onSuccess(JavaScriptObject result) {
2025 // TODO
2026 }
2027
2028 @Override
2029 public void onFailure(Throwable caught) {
2030 // never invoked
2031 }
2032});
2033----
2034
2035Example for invoking a REST endpoint defined by a plugin:
2036
2037[source,java]
2038----
2039new RestApi("projects").id(projectName).view("myplugin", "myview")
2040 .get(new AsyncCallback<JavaScriptObject>() {
2041
2042 @Override
2043 public void onSuccess(JavaScriptObject result) {
2044 // TODO
2045 }
2046
2047 @Override
2048 public void onFailure(Throwable caught) {
2049 // never invoked
2050 }
2051});
2052----
2053
2054The `onFailure(Throwable)` of the provided callback is never invoked.
2055If an error occurs, it is shown in an error dialog.
2056
2057In order to be able to do REST calls the GWT module must inherit
2058`com.google.gwt.json.JSON`:
2059
2060[source,xml]
2061----
2062<inherits name="com.google.gwt.json.JSON"/>
2063----
2064
Edwin Kempin15199792014-04-23 16:22:05 +02002065[[screen]]
Shawn Pearced5c844f2013-12-26 15:32:26 -08002066== Add Screen
Edwin Kempin15199792014-04-23 16:22:05 +02002067A link:#gwt_ui_extension[GWT plugin] can link:#top-menu-extensions[add
2068a menu item] that opens a screen that is implemented by the plugin.
2069This way plugin screens can be fully integrated into the Gerrit UI.
Shawn Pearced5c844f2013-12-26 15:32:26 -08002070
2071Example menu item:
2072[source,java]
2073----
2074public class MyMenu implements TopMenu {
2075 private final List<MenuEntry> menuEntries;
2076
2077 @Inject
2078 public MyMenu(@PluginName String name) {
David Pursehouseccdeae82016-05-03 23:16:15 +09002079 menuEntries = new ArrayList<>();
Shawn Pearced5c844f2013-12-26 15:32:26 -08002080 menuEntries.add(new MenuEntry("My Menu", Collections.singletonList(
2081 new MenuItem("My Screen", "#/x/" + name + "/my-screen", ""))));
2082 }
2083
2084 @Override
2085 public List<MenuEntry> getEntries() {
2086 return menuEntries;
2087 }
2088}
2089----
2090
2091Example screen:
2092[source,java]
2093----
2094public class MyPlugin extends PluginEntryPoint {
2095 @Override
2096 public void onPluginLoad() {
2097 Plugin.get().screen("my-screen", new Screen.EntryPoint() {
2098 @Override
2099 public void onLoad(Screen screen) {
2100 screen.add(new InlineLabel("My Screen");
2101 screen.show();
2102 }
2103 });
2104 }
2105}
2106----
2107
Edwin Kempin70e11122015-07-08 13:28:40 +02002108[[user-settings-screen]]
2109== Add User Settings Screen
2110
2111A link:#gwt_ui_extension[GWT plugin] can implement a user settings
2112screen that is integrated into the Gerrit user settings menu.
2113
2114Example settings screen:
2115[source,java]
2116----
2117public class MyPlugin extends PluginEntryPoint {
2118 @Override
2119 public void onPluginLoad() {
2120 Plugin.get().settingsScreen("my-preferences", "My Preferences",
2121 new Screen.EntryPoint() {
2122 @Override
2123 public void onLoad(Screen screen) {
2124 screen.setPageTitle("Settings");
2125 screen.add(new InlineLabel("My Preferences"));
2126 screen.show();
2127 }
2128 });
2129 }
2130}
2131----
2132
Edwin Kempinfa0d4942015-07-16 12:38:52 +02002133By defining an link:config-gerrit.html#urlAlias[urlAlias] Gerrit
2134administrators can map plugin screens into the Gerrit URL namespace or
2135even replace Gerrit screens by plugin screens.
2136
Edwin Kempinb1e6a3a2015-07-22 15:36:56 +02002137Plugins may also programatically add URL aliases in the preferences of
2138of a user. This way certain screens can be replaced for certain users.
2139E.g. the plugin may offer a user preferences setting for choosing a
2140screen that then sets/unsets a URL alias for the user.
2141
Edwin Kempin289f1a02014-02-04 16:08:25 +01002142[[settings-screen]]
2143== Plugin Settings Screen
2144
2145If a plugin implements a screen for administrating its settings that is
2146available under "#/x/<plugin-name>/settings" it is automatically linked
2147from the plugin list screen.
2148
Edwin Kempinf5a77332012-07-18 11:17:53 +02002149[[http]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002150== HTTP Servlets
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002151
2152Plugins or extensions may register additional HTTP servlets, and
2153wrap them with HTTP filters.
2154
2155Servlets may use auto-registration to declare the URL they handle:
2156
David Pursehouse68153d72013-09-04 10:09:17 +09002157[source,java]
2158----
2159import com.google.gerrit.extensions.annotations.Export;
2160import com.google.inject.Singleton;
2161import javax.servlet.http.HttpServlet;
2162import javax.servlet.http.HttpServletRequest;
2163import javax.servlet.http.HttpServletResponse;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002164
David Pursehouse68153d72013-09-04 10:09:17 +09002165@Export("/print")
2166@Singleton
2167class HelloServlet extends HttpServlet {
2168 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
2169 res.setContentType("text/plain");
2170 res.setCharacterEncoding("UTF-8");
2171 res.getWriter().write("Hello");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002172 }
David Pursehouse68153d72013-09-04 10:09:17 +09002173}
2174----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002175
Edwin Kempin8aa650f2012-07-18 11:25:48 +02002176The auto registration only works for standard servlet mappings like
Jonathan Nieder5758f182015-03-30 11:28:55 -07002177`/foo` or `+/foo/*+`. Regex style bindings must use a Guice ServletModule
Edwin Kempin8aa650f2012-07-18 11:25:48 +02002178to register the HTTP servlets and declare it explicitly in the manifest
2179with the `Gerrit-HttpModule` attribute:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002180
David Pursehouse68153d72013-09-04 10:09:17 +09002181[source,java]
2182----
2183import com.google.inject.servlet.ServletModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002184
David Pursehouse68153d72013-09-04 10:09:17 +09002185class MyWebUrls extends ServletModule {
2186 protected void configureServlets() {
2187 serve("/print").with(HelloServlet.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002188 }
David Pursehouse68153d72013-09-04 10:09:17 +09002189}
2190----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002191
2192For a plugin installed as name `helloworld`, the servlet implemented
2193by HelloServlet class will be available to users as:
2194
2195----
2196$ curl http://review.example.com/plugins/helloworld/print
2197----
Nasser Grainawie033b262012-05-09 17:54:21 -07002198
Edwin Kempinf5a77332012-07-18 11:17:53 +02002199[[data-directory]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002200== Data Directory
Edwin Kempin41f63912012-07-17 12:33:55 +02002201
Dave Borowitz9e158752015-02-24 10:17:04 -08002202Plugins can request a data directory with a `@PluginData` Path (or File,
2203deprecated) dependency. A data directory will be created automatically
2204by the server in `$site_path/data/$plugin_name` and passed to the
2205plugin.
Edwin Kempin41f63912012-07-17 12:33:55 +02002206
2207Plugins can use this to store any data they want.
2208
David Pursehouse68153d72013-09-04 10:09:17 +09002209[source,java]
2210----
2211@Inject
Dave Borowitz9e158752015-02-24 10:17:04 -08002212MyType(@PluginData java.nio.file.Path myDir) {
2213 this.in = Files.newInputStream(myDir.resolve("my.config"));
David Pursehouse68153d72013-09-04 10:09:17 +09002214}
2215----
Edwin Kempin41f63912012-07-17 12:33:55 +02002216
Dariusz Lukszaebab92a2014-09-10 11:14:19 +02002217[[secure-store]]
2218== SecureStore
2219
2220SecureStore allows to change the way Gerrit stores sensitive data like
2221passwords.
2222
2223In order to replace the default SecureStore (no-op) implementation,
2224a class that extends `com.google.gerrit.server.securestore.SecureStore`
2225needs to be provided (with dependencies) in a separate jar file. Then
2226link:pgm-SwitchSecureStore.html[SwitchSecureStore] must be run to
2227switch implementations.
2228
2229The SecureStore implementation is instantiated using a Guice injector
2230which binds the `File` annotated with the `@SitePath` annotation.
2231This means that a SecureStore implementation class can get access to
2232the `site_path` like in the following example:
2233
2234[source,java]
2235----
2236@Inject
2237MySecureStore(@SitePath java.io.File sitePath) {
2238 // your code
2239}
2240----
2241
2242No Guice bindings or modules are required. Gerrit will automatically
2243discover and bind the implementation.
2244
Michael Ochmann24612652016-02-12 17:26:18 +01002245[[accountcreation]]
2246== Account Creation
2247
2248Plugins can hook into the
2249link:rest-api-accounts.html#create-account[account creation] REST API and
2250inject additional external identifiers for an account that represents a user
2251in some external user store. For that, an implementation of the extension
2252point `com.google.gerrit.server.api.accounts.AccountExternalIdCreator`
2253must be registered.
2254
2255[source,java]
2256----
2257class MyExternalIdCreator implements AccountExternalIdCreator {
2258 @Override
2259 public List<AccountExternalId> create(Account.Id id, String username,
2260 String email) {
2261 // your code
2262 }
2263}
2264
2265bind(AccountExternalIdCreator.class)
2266 .annotatedWith(UniqueAnnotations.create())
2267 .to(MyExternalIdCreator.class);
2268}
2269----
2270
Edwin Kempinea621482013-10-16 12:58:24 +02002271[[download-commands]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002272== Download Commands
Edwin Kempinea621482013-10-16 12:58:24 +02002273
Edwin Kempineafde882015-05-11 15:40:44 +02002274Gerrit offers commands for downloading changes and cloning projects
2275using different download schemes (e.g. for downloading via different
2276network protocols). Plugins can contribute download schemes, download
2277commands and clone commands by implementing
2278`com.google.gerrit.extensions.config.DownloadScheme`,
2279`com.google.gerrit.extensions.config.DownloadCommand` and
2280`com.google.gerrit.extensions.config.CloneCommand`.
Edwin Kempinea621482013-10-16 12:58:24 +02002281
Edwin Kempineafde882015-05-11 15:40:44 +02002282The download schemes, download commands and clone commands which are
2283used most often are provided by the Gerrit core plugin
2284`download-commands`.
Edwin Kempinea621482013-10-16 12:58:24 +02002285
Edwin Kempin78279ba2015-05-22 15:22:41 +02002286[[included-in]]
2287== Included In
2288
2289For merged changes the link:user-review-ui.html#included-in[Included In]
2290drop-down panel shows the branches and tags in which the change is
2291included.
2292
2293Plugins can add additional systems in which the change can be included
2294by implementing `com.google.gerrit.extensions.config.ExternalIncludedIn`,
2295e.g. a plugin can provide a list of servers on which the change was
2296deployed.
2297
Sven Selbergae1a10c2014-02-14 14:24:29 +01002298[[links-to-external-tools]]
2299== Links To External Tools
2300
2301Gerrit has extension points that enables development of a
2302light-weight plugin that links commits to external
2303tools (GitBlit, CGit, company specific resources etc).
2304
2305PatchSetWebLinks will appear to the right of the commit-SHA1 in the UI.
2306
2307[source, java]
2308----
2309import com.google.gerrit.extensions.annotations.Listen;
2310import com.google.gerrit.extensions.webui.PatchSetWebLink;;
Sven Selberga85e64d2014-09-24 10:52:21 +02002311import com.google.gerrit.extensions.webui.WebLinkTarget;
Sven Selbergae1a10c2014-02-14 14:24:29 +01002312
2313@Listen
2314public class MyWeblinkPlugin implements PatchSetWebLink {
2315
2316 private String name = "MyLink";
2317 private String placeHolderUrlProjectCommit = "http://my.tool.com/project=%s/commit=%s";
Sven Selberg55484202014-06-26 08:48:51 +02002318 private String imageUrl = "http://placehold.it/16x16.gif";
Sven Selbergae1a10c2014-02-14 14:24:29 +01002319
2320 @Override
Jonathan Niederb3cd6902015-03-12 16:19:15 -07002321 public WebLinkInfo getPatchSetWebLink(String projectName, String commit) {
Sven Selberga85e64d2014-09-24 10:52:21 +02002322 return new WebLinkInfo(name,
2323 imageUrl,
2324 String.format(placeHolderUrlProjectCommit, project, commit),
2325 WebLinkTarget.BLANK);
Edwin Kempinceeed6b2014-09-11 17:07:33 +02002326 }
Sven Selbergae1a10c2014-02-14 14:24:29 +01002327}
2328----
2329
David Pursehouse58b8d762016-12-09 11:12:27 +09002330ParentWebLinks will appear to the right of the SHA1 of the parent
2331revisions in the UI. The implementation should in most use cases direct
2332to the same external service as PatchSetWebLink; it is provided as a
2333separate interface because not all users want to have links for the
2334parent revisions.
2335
Edwin Kempinb3696c82014-09-11 09:41:42 +02002336FileWebLinks will appear in the side-by-side diff screen on the right
2337side of the patch selection on each side.
2338
Edwin Kempin8cdce502014-12-06 10:55:38 +01002339DiffWebLinks will appear in the side-by-side and unified diff screen in
2340the header next to the navigation icons.
2341
Edwin Kempinea004752014-04-11 15:56:02 +02002342ProjectWebLinks will appear in the project list in the
2343`Repository Browser` column.
2344
Edwin Kempin0f697bd2014-09-10 18:23:29 +02002345BranchWebLinks will appear in the branch list in the last column.
2346
Edwin Kempinf82b8122016-06-03 09:20:16 +02002347FileHistoryWebLinks will appear on the access rights screen.
2348
Paladox none34da15c2017-07-01 14:49:10 +00002349TagWebLinks will appear in the tag list in the last column.
2350
Dave Borowitzd0c01fd2017-06-06 10:47:08 -04002351If a `get*WebLink` implementation returns `null`, the link will be omitted. This
2352allows the plugin to selectively "enable" itself on a per-project/branch/file
2353basis.
2354
Saša Živkovca7a67e2015-12-01 14:25:10 +01002355[[lfs-extension]]
2356== LFS Storage Plugins
2357
David Pursehouse2463c542016-08-02 16:04:58 +09002358Gerrit provides an extension point that enables development of
2359link:https://github.com/github/git-lfs/blob/master/docs/api/v1/http-v1-batch.md[
2360LFS (Large File Storage)] storage plugins. Gerrit core exposes the default LFS
2361protocol endpoint `<project-name>/info/lfs/objects/batch` and forwards the requests
2362to the configured link:config-gerrit.html#lfs[lfs.plugin] plugin which implements
2363the LFS protocol. By exposing the default LFS endpoint, the git-lfs client can be
2364used without any configuration.
Saša Živkovca7a67e2015-12-01 14:25:10 +01002365
2366[source, java]
2367----
2368/** Provide an LFS protocol implementation */
2369import org.eclipse.jgit.lfs.server.LargeFileRepository;
2370import org.eclipse.jgit.lfs.server.LfsProtocolServlet;
2371
2372@Singleton
2373public class LfsApiServlet extends LfsProtocolServlet {
2374 private static final long serialVersionUID = 1L;
2375
2376 private final S3LargeFileRepository repository;
2377
2378 @Inject
2379 LfsApiServlet(S3LargeFileRepository repository) {
2380 this.repository = repository;
2381 }
2382
2383 @Override
2384 protected LargeFileRepository getLargeFileRepository() {
2385 return repository;
2386 }
2387}
2388
2389/** Register the LfsApiServlet to listen on the default LFS protocol endpoint */
2390import static com.google.gerrit.httpd.plugins.LfsPluginServlet.URL_REGEX;
2391
2392import com.google.gerrit.httpd.plugins.HttpPluginModule;
2393
2394public class HttpModule extends HttpPluginModule {
2395
2396 @Override
2397 protected void configureServlets() {
2398 serveRegex(URL_REGEX).with(LfsApiServlet.class);
2399 }
2400}
2401
2402/** Provide an implementation of the LargeFileRepository */
2403import org.eclipse.jgit.lfs.server.s3.S3Repository;
2404
2405public class S3LargeFileRepository extends S3Repository {
2406...
2407}
2408----
2409
David Pursehouse8ad11732016-08-29 15:00:14 +09002410[[metrics]]
2411== Metrics
2412
2413=== Metrics Reporting
2414
2415To send Gerrit's metrics data to an external reporting backend, a plugin can
2416get a `MetricRegistry` injected and register an instance of a class that
2417implements the `Reporter` interface from link:http://metrics.dropwizard.io/[
2418DropWizard Metrics].
2419
2420Metric reporting plugin implementations are provided for
2421link:https://gerrit.googlesource.com/plugins/metrics-reporter-jmx/[JMX],
2422link:https://gerrit.googlesource.com/plugins/metrics-reporter-elasticsearch/[Elastic Search],
2423and link:https://gerrit.googlesource.com/plugins/metrics-reporter-graphite/[Graphite].
2424
2425There is also a working example of reporting metrics to the console in the
Eryk Szymanskida073b12017-09-11 14:27:57 +02002426link: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 +09002427cookbook plugin].
2428
2429=== Providing own metrics
2430
2431Plugins may provide metrics to be dispatched to external reporting services by
2432getting a `MetricMaker` injected and creating instances of specific types of
2433metric:
2434
2435* Counter
2436+
2437Metric whose value increments during the life of the process.
2438
2439* Timer
2440+
2441Metric recording time spent on an operation.
2442
2443* Histogram
2444+
2445Metric recording statistical distribution (rate) of values.
2446
David Pursehouse48d05ea2017-02-03 19:05:29 +09002447Note that metrics cannot be recorded from plugin init steps that
2448are run during site initialization.
2449
David Pursehousec3bbd562017-02-06 20:25:29 +09002450By default, plugin metrics are recorded under
2451`plugins/${plugin-name}/${metric-name}`. This can be changed by
2452setting `plugins.${plugin-name}.metricsPrefix` in the `gerrit.config`
2453file. For example:
2454
2455----
2456 [plugin "my-plugin"]
2457 metricsPrefix = my-metrics
2458----
2459
2460will cause the metrics to be recorded under `my-metrics/${metric-name}`.
David Pursehouse8ad11732016-08-29 15:00:14 +09002461
2462See the replication metrics in the
2463link:https://gerrit.googlesource.com/plugins/replication/+/master/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationMetrics.java[
2464replication plugin] for an example of usage.
2465
Edwin Kempinda17bc32016-06-14 11:50:58 +02002466[[account-patch-review-store]]
2467== AccountPatchReviewStore
2468
2469The AccountPatchReviewStore is used to store reviewed flags on changes.
2470A reviewed flag is a tuple of (patch set ID, file, account ID) and
2471records whether the user has reviewed a file in a patch set. Each user
2472can easily have thousands of reviewed flags and the number of reviewed
2473flags is growing without bound. The store must be able handle this data
2474volume efficiently.
2475
2476Gerrit implements this extension point, but plugins may bind another
2477implementation, e.g. one that supports multi-master.
2478
2479----
2480DynamicItem.bind(binder(), AccountPatchReviewStore.class)
2481 .to(MultiMasterAccountPatchReviewStore.class);
2482
2483...
2484
2485public class MultiMasterAccountPatchReviewStore
2486 implements AccountPatchReviewStore {
2487 ...
2488}
2489----
2490
Dave Borowitzf1790ce2016-11-14 12:26:12 -08002491
Edwin Kempinf5a77332012-07-18 11:17:53 +02002492[[documentation]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002493== Documentation
Nasser Grainawie033b262012-05-09 17:54:21 -07002494
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002495If a plugin does not register a filter or servlet to handle URLs
Jonathan Nieder5758f182015-03-30 11:28:55 -07002496`+/Documentation/*+` or `+/static/*+`, the core Gerrit server will
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002497automatically export these resources over HTTP from the plugin JAR.
2498
David Pursehouse6853b5a2013-07-10 11:38:03 +09002499Static resources under the `static/` directory in the JAR will be
Dave Borowitzb893ac82013-03-27 10:03:55 -04002500available as `/plugins/helloworld/static/resource`. This prefix is
2501configurable by setting the `Gerrit-HttpStaticPrefix` attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002502
David Pursehouse6853b5a2013-07-10 11:38:03 +09002503Documentation files under the `Documentation/` directory in the JAR
Dave Borowitzb893ac82013-03-27 10:03:55 -04002504will be available as `/plugins/helloworld/Documentation/resource`. This
2505prefix is configurable by setting the `Gerrit-HttpDocumentationPrefix`
2506attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002507
Christian Aistleitner040cf822015-03-26 21:09:09 +01002508Documentation may be written in the Markdown flavor
2509link:https://github.com/sirthias/pegdown[pegdown]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002510if the file name ends with `.md`. Gerrit will automatically convert
2511Markdown to HTML if accessed with extension `.html`.
Nasser Grainawie033b262012-05-09 17:54:21 -07002512
Edwin Kempinf5a77332012-07-18 11:17:53 +02002513[[macros]]
Edwin Kempinc78777d2012-07-16 15:55:11 +02002514Within the Markdown documentation files macros can be used that allow
2515to write documentation with reasonably accurate examples that adjust
2516automatically based on the installation.
2517
2518The following macros are supported:
2519
2520[width="40%",options="header"]
2521|===================================================
2522|Macro | Replacement
2523|@PLUGIN@ | name of the plugin
2524|@URL@ | Gerrit Web URL
2525|@SSH_HOST@ | SSH Host
2526|@SSH_PORT@ | SSH Port
2527|===================================================
2528
2529The macros will be replaced when the documentation files are rendered
2530from Markdown to HTML.
2531
2532Macros that start with `\` such as `\@KEEP@` will render as `@KEEP@`
2533even if there is an expansion for `KEEP` in the future.
2534
Edwin Kempinf5a77332012-07-18 11:17:53 +02002535[[auto-index]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002536=== Automatic Index
Shawn O. Pearce795167c2012-05-12 11:20:18 -07002537
2538If a plugin does not handle its `/` URL itself, Gerrit will
2539redirect clients to the plugin's `/Documentation/index.html`.
2540Requests for `/Documentation/` (bare directory) will also redirect
2541to `/Documentation/index.html`.
2542
2543If neither resource `Documentation/index.html` or
2544`Documentation/index.md` exists in the plugin JAR, Gerrit will
2545automatically generate an index page for the plugin's documentation
2546tree by scanning every `*.md` and `*.html` file in the Documentation/
2547directory.
2548
2549For any discovered Markdown (`*.md`) file, Gerrit will parse the
2550header of the file and extract the first level one title. This
2551title text will be used as display text for a link to the HTML
2552version of the page.
2553
2554For any discovered HTML (`*.html`) file, Gerrit will use the name
2555of the file, minus the `*.html` extension, as the link text. Any
2556hyphens in the file name will be replaced with spaces.
2557
David Pursehouse6853b5a2013-07-10 11:38:03 +09002558If a discovered file is named `about.md` or `about.html`, its
2559content will be inserted in an 'About' section at the top of the
2560auto-generated index page. If both `about.md` and `about.html`
2561exist, only the first discovered file will be used.
2562
Shawn O. Pearce795167c2012-05-12 11:20:18 -07002563If a discovered file name beings with `cmd-` it will be clustered
David Pursehouse6853b5a2013-07-10 11:38:03 +09002564into a 'Commands' section of the generated index page.
2565
David Pursehousefe529152013-08-14 16:35:06 +09002566If a discovered file name beings with `servlet-` it will be clustered
2567into a 'Servlets' section of the generated index page.
2568
2569If a discovered file name beings with `rest-api-` it will be clustered
2570into a 'REST APIs' section of the generated index page.
2571
David Pursehouse6853b5a2013-07-10 11:38:03 +09002572All other files are clustered under a 'Documentation' section.
Shawn O. Pearce795167c2012-05-12 11:20:18 -07002573
2574Some optional information from the manifest is extracted and
2575displayed as part of the index page, if present in the manifest:
2576
2577[width="40%",options="header"]
2578|===================================================
2579|Field | Source Attribute
2580|Name | Implementation-Title
2581|Vendor | Implementation-Vendor
2582|Version | Implementation-Version
2583|URL | Implementation-URL
2584|API Version | Gerrit-ApiVersion
2585|===================================================
2586
Edwin Kempinf5a77332012-07-18 11:17:53 +02002587[[deployment]]
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002588== Deployment
Nasser Grainawie033b262012-05-09 17:54:21 -07002589
Edwin Kempinf7295742012-07-16 15:03:46 +02002590Compiled plugins and extensions can be deployed to a running Gerrit
2591server using the link:cmd-plugin-install.html[plugin install] command.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002592
David Pursehouse00c70812017-07-31 10:57:26 +01002593Web UI plugins distributed as a single `.js` file (or `.html' file for
2594Polygerrit) can be deployed without the overhead of JAR packaging. For
2595more information refer to link:cmd-plugin-install.html[plugin install]
2596command.
Dariusz Luksza357a2422012-11-12 06:16:26 +01002597
David Pursehouse75021fe2017-07-31 23:07:04 +02002598Plugins can also be copied directly into the server's directory at
2599`$site_path/plugins/$name.(jar|js|html)`. For Web UI plugins, the name
2600of the file, minus the `.js` or `.html` extension, will be used as the
2601plugin name. For JAR plugins, the value of the `Gerrit-PluginName`
2602manifest attribute will be used, if provided, otherwise the name of
2603the file, minus the `.jar` extension, will be used.
2604
2605For Web UI plugins, the plugin version is derived from the filename.
2606If the filename contains one or more hyphens, the version is taken
2607from the portion following the last hyphen. For example if the plugin
2608filename is `my-plugin-1.0.js` the version will be `1.0`. For JAR
2609plugins, the version is taken from the `Version` attribute in the
2610manifest.
2611
2612Unless disabled, servers periodically scan the `$site_path/plugins`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -07002613directory for updated plugins. The time can be adjusted by
2614link:config-gerrit.html#plugins.checkFrequency[plugins.checkFrequency].
Deniz Türkoglueb78b602012-05-07 14:02:36 -07002615
Edwin Kempinf7295742012-07-16 15:03:46 +02002616For disabling plugins the link:cmd-plugin-remove.html[plugin remove]
2617command can be used.
2618
Brad Larsond5e87c32012-07-11 12:18:49 -05002619Disabled plugins can be re-enabled using the
2620link:cmd-plugin-enable.html[plugin enable] command.
2621
Edwin Kempinc1a25102015-06-22 14:47:36 +02002622== Known issues and bugs
2623
2624=== Error handling in UI when using the REST API
2625
2626When a plugin invokes a REST endpoint in the UI, it provides an
2627`AsyncCallback` to handle the result. At the moment the
2628`onFailure(Throwable)` of the callback is never invoked, even if there
2629is an error. Errors are always handled by the Gerrit core UI which
2630shows the error dialog. This means currently plugins cannot do any
2631error handling and e.g. ignore expected errors.
2632
Han-Wen Nienhuys84d830b2017-02-15 16:36:04 +01002633In the following example the REST endpoint would return '404 Not
2634Found' if the user has no username and the Gerrit core UI would
2635display an error dialog for this. However having no username is
2636not an error and the plugin may like to handle this case.
Edwin Kempinc1a25102015-06-22 14:47:36 +02002637
2638[source,java]
2639----
Han-Wen Nienhuys84d830b2017-02-15 16:36:04 +01002640new RestApi("accounts").id("self").view("username")
Edwin Kempinc1a25102015-06-22 14:47:36 +02002641 .get(new AsyncCallback<NativeString>() {
2642
2643 @Override
Han-Wen Nienhuys84d830b2017-02-15 16:36:04 +01002644 public void onSuccess(NativeString username) {
Edwin Kempinc1a25102015-06-22 14:47:36 +02002645 // TODO
2646 }
2647
2648 @Override
2649 public void onFailure(Throwable caught) {
2650 // never invoked
2651 }
2652});
2653----
2654
2655
Patrick Hiesel87880b02016-05-03 18:15:08 +02002656[[reviewer-suggestion]]
2657== Reviewer Suggestion Plugins
2658
2659Gerrit provides an extension point that enables Plugins to rank
2660the list of reviewer suggestion a user receives upon clicking "Add Reviewer" on
2661the change screen.
2662Gerrit supports both a default suggestion that appears when the user has not yet
2663typed anything and a filtered suggestion that is shown as the user starts
2664typing.
2665Plugins receive a candidate list and can return a Set of suggested reviewers
2666containing the Account.Id and a score for each reviewer.
2667The candidate list is non-binding and plugins can choose to return reviewers not
2668initially contained in the candidate list.
2669Server administrators can configure the overall weight of each plugin using the
2670weight config parameter on [addreviewer "<pluginName-exportName>"].
2671
2672[source, java]
2673----
Patrick Hiesel2514e412016-10-13 09:34:44 +02002674import com.google.gerrit.common.Nullable;
2675import com.google.gerrit.extensions.annotations.ExtensionPoint;
Patrick Hiesel87880b02016-05-03 18:15:08 +02002676import com.google.gerrit.reviewdb.client.Account;
2677import com.google.gerrit.reviewdb.client.Change;
Patrick Hiesel2514e412016-10-13 09:34:44 +02002678import com.google.gerrit.reviewdb.client.Project;
Patrick Hiesel87880b02016-05-03 18:15:08 +02002679
2680import java.util.Set;
2681
2682public class MyPlugin implements ReviewerSuggestion {
Patrick Hiesel2514e412016-10-13 09:34:44 +02002683 public Set<SuggestedReviewer> suggestReviewers(Project.NameKey project,
2684 @Nullable Change.Id changeId, @Nullable String query,
2685 Set<Account.Id> candidates) {
2686 Set<SuggestedReviewer> suggestions = new HashSet<>();
2687 // Implement your ranking logic here
2688 return suggestions;
Patrick Hiesel87880b02016-05-03 18:15:08 +02002689 }
2690}
2691----
2692
2693
Patrick Hiesel30c76182017-01-20 11:46:43 +01002694[[mail-filter]]
2695== Mail Filter Plugins
2696
2697Gerrit provides an extension point that enables Plugins to discard incoming
2698messages and prevent further processing by Gerrit.
2699
David Pursehouse4b067752017-03-03 15:54:53 +09002700This can be used to implement spam checks, signature validations or organization
Patrick Hiesel30c76182017-01-20 11:46:43 +01002701specific checks like IP filters.
2702
2703[source, java]
2704----
2705import com.google.gerrit.extensions.annotations.ExtensionPoint;
2706import com.google.gerrit.server.mail.receive.MailMessage;
2707
2708public class MyPlugin implements MailFilter {
2709 boolean shouldProcessMessage(MailMessage message) {
2710 // Implement your filter logic here
2711 return true;
2712 }
2713}
2714----
2715
Yuxuan 'fishy' Wang61698b12013-12-20 12:55:51 -08002716== SEE ALSO
David Ostrovskyf86bae52013-09-01 09:10:39 +02002717
2718* link:js-api.html[JavaScript API]
2719* link:dev-rest-api.html[REST API Developers' Notes]
2720
Deniz Türkoglueb78b602012-05-07 14:02:36 -07002721GERRIT
2722------
2723Part of link:index.html[Gerrit Code Review]
Yuxuan 'fishy' Wang99cb68d2013-10-31 17:26:00 -07002724
2725SEARCHBOX
2726---------