Allow to configure a URL that explains how to get a code owner override
Sometimes a change needs to be submitted urgently and there is no time
to gather the required code owner approvals. In this case a code owner
override can be applied to make the change submittable. Usually the
permission to apply an override is limited to a small set of super
users that may not be known to the user needing a code owner override.
To allow users to find a person who can grant a code owner override we
allow to configure a URL to a documentation page where it's explained
how an override can be retrieved. This documentation page is specific
for a host or project. The frontend gets the URL via the
GetCodeOwnerProjectConfig REST endpoint and can display a link or icon
for it.
Signed-off-by: Edwin Kempin <ekempin@google.com>
Change-Id: Ice075c75f379491ded97d1f0dd8d06a942cd4188
diff --git a/java/com/google/gerrit/plugins/codeowners/api/GeneralInfo.java b/java/com/google/gerrit/plugins/codeowners/api/GeneralInfo.java
index c6cb2c2..8562057 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/GeneralInfo.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/GeneralInfo.java
@@ -37,4 +37,10 @@
* <p>Not set, if {@code false}.
*/
public Boolean implicitApprovals;
+
+ /**
+ * Optional URL for a page that provides project/host-specific information about how to request a
+ * code owner override.
+ */
+ public String overrideInfoUrl;
}
diff --git a/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfiguration.java b/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfiguration.java
index 1c6f591..236b12e 100644
--- a/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfiguration.java
+++ b/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfiguration.java
@@ -154,6 +154,17 @@
}
/**
+ * Gets the override info URL that is configured for the given project.
+ *
+ * @param project the project for which the configured override info URL should be returned
+ * @return the override info URL that is configured for the given project
+ */
+ public Optional<String> getOverrideInfoUrl(Project.NameKey project) {
+ requireNonNull(project, "project");
+ return generalConfig.getOverrideInfoUrl(getPluginConfig(project));
+ }
+
+ /**
* Returns the email domains that are allowed to be used for code owners.
*
* @return the email domains that are allowed to be used for code owners, an empty set if all
diff --git a/java/com/google/gerrit/plugins/codeowners/config/GeneralConfig.java b/java/com/google/gerrit/plugins/codeowners/config/GeneralConfig.java
index f764e71..284413c 100644
--- a/java/com/google/gerrit/plugins/codeowners/config/GeneralConfig.java
+++ b/java/com/google/gerrit/plugins/codeowners/config/GeneralConfig.java
@@ -69,6 +69,8 @@
@VisibleForTesting
public static final String KEY_ENABLE_IMPLICIT_APPROVALS = "enableImplicitApprovals";
+ @VisibleForTesting public static final String KEY_OVERRIDE_INFO_URL = "overrideInfoUrl";
+
private final String pluginName;
private final PluginConfig pluginConfigFromGerritConfig;
@@ -275,4 +277,25 @@
.map(CodeOwnerReference::create)
.collect(toImmutableSet());
}
+
+ /**
+ * Gets an URL that leads to an information page about overrides.
+ *
+ * <p>The URL is retrieved from the given plugin config, with fallback to the {@code
+ * gerrit.config}.
+ *
+ * @param pluginConfig the plugin config from which the override info URL should be read.
+ * @return URL that leads to an information page about overrides, {@link Optional#empty()} if no
+ * such URL is configured
+ */
+ Optional<String> getOverrideInfoUrl(Config pluginConfig) {
+ requireNonNull(pluginConfig, "pluginConfig");
+
+ String fileExtension = pluginConfig.getString(SECTION_CODE_OWNERS, null, KEY_OVERRIDE_INFO_URL);
+ if (fileExtension != null) {
+ return Optional.of(fileExtension);
+ }
+
+ return Optional.ofNullable(pluginConfigFromGerritConfig.getString(KEY_OVERRIDE_INFO_URL));
+ }
}
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJson.java b/java/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJson.java
index 4349d05..846b1c2 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJson.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJson.java
@@ -76,6 +76,8 @@
codeOwnersPluginConfiguration.getMergeCommitStrategy(projectName);
generalInfo.implicitApprovals =
codeOwnersPluginConfiguration.areImplicitApprovalsEnabled(projectName) ? true : null;
+ generalInfo.overrideInfoUrl =
+ codeOwnersPluginConfiguration.getOverrideInfoUrl(projectName).orElse(null);
return generalInfo;
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerProjectConfigIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerProjectConfigIT.java
index 8f9312b..5b879dc 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerProjectConfigIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerProjectConfigIT.java
@@ -87,6 +87,7 @@
assertThat(codeOwnerProjectConfigInfo.general.mergeCommitStrategy)
.isEqualTo(MergeCommitStrategy.ALL_CHANGED_FILES);
assertThat(codeOwnerProjectConfigInfo.general.implicitApprovals).isNull();
+ assertThat(codeOwnerProjectConfigInfo.general.overrideInfoUrl).isNull();
assertThat(codeOwnerProjectConfigInfo.status.disabled).isNull();
assertThat(codeOwnerProjectConfigInfo.status.disabledBranches).isNull();
assertThat(codeOwnerProjectConfigInfo.backend.idsByBranch).isNull();
@@ -109,6 +110,15 @@
}
@Test
+ public void getConfigWithConfiguredOverrideInfoUrl() throws Exception {
+ configureOverrideInfoUrl(project, "http://foo.example.com");
+ CodeOwnerProjectConfigInfo codeOwnerProjectConfigInfo =
+ projectCodeOwnersApiFactory.project(project).getConfig();
+ assertThat(codeOwnerProjectConfigInfo.general.overrideInfoUrl)
+ .isEqualTo("http://foo.example.com");
+ }
+
+ @Test
public void getConfigWithConfiguredMergeCommitStrategy() throws Exception {
configureMergeCommitStrategy(project, MergeCommitStrategy.FILES_WITH_CONFLICT_RESOLUTION);
CodeOwnerProjectConfigInfo codeOwnerProjectConfigInfo =
@@ -224,6 +234,11 @@
setConfig(project, null, GeneralConfig.KEY_FILE_EXTENSION, fileExtension);
}
+ private void configureOverrideInfoUrl(Project.NameKey project, String overrideInfoUrl)
+ throws Exception {
+ setConfig(project, null, GeneralConfig.KEY_OVERRIDE_INFO_URL, overrideInfoUrl);
+ }
+
private void configureMergeCommitStrategy(
Project.NameKey project, MergeCommitStrategy mergeCommitStrategy) throws Exception {
setConfig(project, null, GeneralConfig.KEY_MERGE_COMMIT_STRATEGY, mergeCommitStrategy.name());
diff --git a/javatests/com/google/gerrit/plugins/codeowners/config/GeneralConfigTest.java b/javatests/com/google/gerrit/plugins/codeowners/config/GeneralConfigTest.java
index 3037cd4..c4b8a8f 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/config/GeneralConfigTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/config/GeneralConfigTest.java
@@ -21,6 +21,7 @@
import static com.google.gerrit.plugins.codeowners.config.GeneralConfig.KEY_FILE_EXTENSION;
import static com.google.gerrit.plugins.codeowners.config.GeneralConfig.KEY_GLOBAL_CODE_OWNER;
import static com.google.gerrit.plugins.codeowners.config.GeneralConfig.KEY_MERGE_COMMIT_STRATEGY;
+import static com.google.gerrit.plugins.codeowners.config.GeneralConfig.KEY_OVERRIDE_INFO_URL;
import static com.google.gerrit.plugins.codeowners.config.GeneralConfig.KEY_READ_ONLY;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static com.google.gerrit.truth.OptionalSubject.assertThat;
@@ -344,4 +345,34 @@
assertThat(generalConfig.getGlobalCodeOwners(cfg))
.containsExactly(CodeOwnerReference.create("bot3@example.com"));
}
+
+ @Test
+ public void cannotGetOverrideInfoUrlForNullPluginConfig() throws Exception {
+ NullPointerException npe =
+ assertThrows(NullPointerException.class, () -> generalConfig.getOverrideInfoUrl(null));
+ assertThat(npe).hasMessageThat().isEqualTo("pluginConfig");
+ }
+
+ @Test
+ public void noOverrideInfoUrlConfigured() throws Exception {
+ assertThat(generalConfig.getOverrideInfoUrl(new Config())).isEmpty();
+ }
+
+ @Test
+ @GerritConfig(name = "plugin.code-owners.overrideInfoUrl", value = "http://foo.example.com")
+ public void overrideInfoIsRetrievedFromGerritConfigIfNotSpecifiedOnProjectLevel()
+ throws Exception {
+ assertThat(generalConfig.getOverrideInfoUrl(new Config()))
+ .value()
+ .isEqualTo("http://foo.example.com");
+ }
+
+ @Test
+ @GerritConfig(name = "plugin.code-owners.overrideInfoUrl", value = "http://foo.example.com")
+ public void overrideInfoUrlInPluginConfigOverridesOverrideInfoUrlInGerritConfig()
+ throws Exception {
+ Config cfg = new Config();
+ cfg.setString(SECTION_CODE_OWNERS, null, KEY_OVERRIDE_INFO_URL, "http://bar.example.com");
+ assertThat(generalConfig.getOverrideInfoUrl(cfg)).value().isEqualTo("http://bar.example.com");
+ }
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJsonTest.java b/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJsonTest.java
index 4313367..6300a99 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJsonTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJsonTest.java
@@ -144,6 +144,8 @@
when(codeOwnersPluginConfiguration.getBackend(BranchNameKey.create(project, "stable-2.10")))
.thenReturn(protoBackend);
when(codeOwnersPluginConfiguration.getFileExtension(project)).thenReturn(Optional.of("foo"));
+ when(codeOwnersPluginConfiguration.getOverrideInfoUrl(project))
+ .thenReturn(Optional.of("http://foo.example.com"));
when(codeOwnersPluginConfiguration.getMergeCommitStrategy(project))
.thenReturn(MergeCommitStrategy.FILES_WITH_CONFLICT_RESOLUTION);
when(codeOwnersPluginConfiguration.areImplicitApprovalsEnabled(project)).thenReturn(true);
@@ -160,6 +162,8 @@
assertThat(codeOwnerProjectConfigInfo.status.disabled).isTrue();
assertThat(codeOwnerProjectConfigInfo.status.disabledBranches).isNull();
assertThat(codeOwnerProjectConfigInfo.general.fileExtension).isEqualTo("foo");
+ assertThat(codeOwnerProjectConfigInfo.general.overrideInfoUrl)
+ .isEqualTo("http://foo.example.com");
assertThat(codeOwnerProjectConfigInfo.general.mergeCommitStrategy)
.isEqualTo(MergeCommitStrategy.FILES_WITH_CONFLICT_RESOLUTION);
assertThat(codeOwnerProjectConfigInfo.general.implicitApprovals).isTrue();
diff --git a/resources/Documentation/config.md b/resources/Documentation/config.md
index 6b5db3b..905cadf 100644
--- a/resources/Documentation/config.md
+++ b/resources/Documentation/config.md
@@ -61,6 +61,16 @@
`@PLUGIN@.config`.\
By default unset (no file extension is used).
+<a id="pluginCodeOwnersOverrideInfoUrl">plugin.@PLUGIN@.overrideInfoUrl</a>
+: A URL for a page that provides host-specific information about how to
+ request a code owner override.\
+ The frontend displays a link to this page on the change screen so that
+ users can discover the override instructions easily.\
+ Can be overridden per project by setting
+ [codeOwners.overrideInfoUrl](#codeOwnersFileExtension) in
+ `@PLUGIN@.config`.\
+ By default unset (no override info URL).
+
<a id="pluginCodeOwnersEnableImplicitApprovals">plugin.@PLUGIN@.enableImplictApprovals</a>
: Whether an implicit code owner approval from the last uploader is
assumed.\
@@ -264,6 +274,18 @@
[plugin.@PLUGIN@.fileExtension](#pluginCodeOwnersFileExtension) in
`gerrit.config` is used.
+<a id="codeOwnersOverrideInfoUrl">codeOwners.overrideInfoUrl</a>
+: A URL for a page that provides project-specific information about how to
+ request a code owner override.\
+ The frontend displays a link to this page on the change screen so that
+ users can discover the override instructions easily.\
+ Overrides the global setting
+ [plugin.@PLUGIN@.overrideInfoUrl](#pluginCodeOwnersOverrideInfoUrl) in
+ `gerrit.config`.\
+ If not set, the global setting
+ [plugin.@PLUGIN@.overrideInfoUrl](#pluginCodeOwnersOverrideInfoUrl) in
+ `gerrit.config` is used.
+
<a id="codeOwnersEnableImplicitApprovals">codeOwners.enableImplicitApprovals</a>
: Whether an implicit code owner approval from the last uploader is
assumed.\
diff --git a/resources/Documentation/rest-api.md b/resources/Documentation/rest-api.md
index fdcd4da..335daae 100644
--- a/resources/Documentation/rest-api.md
+++ b/resources/Documentation/rest-api.md
@@ -545,6 +545,7 @@
| `file_extension` | optional | The file extension that is used for the code owner config files in this project. Not set if no file extension is used.
| `merge_commit_strategy` || Strategy that defines for merge commits which files require code owner approvals. Can be `ALL_CHANGED_FILES` or `FILES_WITH_CONFLICT_RESOLUTION` (see [mergeCommitStrategy](config.html#pluginCodeOwnersMergeCommitStrategy) for an explanation of these values).
| `implicit_approvals` | optional | Whether an implicit code owner approval from the last uploader is assumed (see [enableImplicitApprovals](config.html#pluginCodeOwnersEnableImplicitApprovals) for details). When unset, `false`.
+| `override_info_url` | optional | Optional URL for a page that provides project/host-specific information about how to request a code owner override.
### <a id="path-code-owner-status-info"> PathCodeOwnerStatusInfo
The `PathCodeOwnerStatusInfo` entity describes the code owner status for a path