aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-plugin-api/src
diff options
context:
space:
mode:
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>2017-01-06 17:06:01 +0100
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>2017-01-13 17:58:12 +0100
commitfb278bdfc64f015f74b757caf37c902ccb50ec67 (patch)
treefa2e33b8b702ef970d235a922f68b20014b106ba /sonar-plugin-api/src
parentc2d588522c2d44c1d274ee506e56b97c8d087fb4 (diff)
downloadsonarqube-fb278bdfc64f015f74b757caf37c902ccb50ec67.tar.gz
sonarqube-fb278bdfc64f015f74b757caf37c902ccb50ec67.zip
SONAR-8581 Create new PagesDefinition API to create a new page in a plugin
Diffstat (limited to 'sonar-plugin-api/src')
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/web/page/Context.java49
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/web/page/Page.java172
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/web/page/PageDefinition.java77
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/web/page/package-info.java25
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/web/page/ContextTest.java72
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/web/page/PageDefinitionTest.java41
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/web/page/PageTest.java136
7 files changed, 572 insertions, 0 deletions
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/web/page/Context.java b/sonar-plugin-api/src/main/java/org/sonar/api/web/page/Context.java
new file mode 100644
index 00000000000..daf2d10390a
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/web/page/Context.java
@@ -0,0 +1,49 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.api.web.page;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import static java.lang.String.format;
+import static java.util.Collections.unmodifiableCollection;
+
+/**
+ * @see PageDefinition
+ * @since 6.3
+ */
+public final class Context {
+ private final Map<String, Page> pagesByPath = new HashMap<>();
+
+ public Context addPage(Page page) {
+ Page existingPageWithSameKey = pagesByPath.putIfAbsent(page.getKey(), page);
+ if (existingPageWithSameKey != null) {
+ throw new IllegalArgumentException(format("Page '%s' cannot be loaded. Another page with key '%s' already exists.", page.getName(), page.getKey()));
+ }
+
+ return this;
+ }
+
+ public Collection<Page> getPages() {
+ return unmodifiableCollection(pagesByPath.values());
+ }
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/web/page/Page.java b/sonar-plugin-api/src/main/java/org/sonar/api/web/page/Page.java
new file mode 100644
index 00000000000..b68ee6bba07
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/web/page/Page.java
@@ -0,0 +1,172 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.api.web.page;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.annotation.CheckForNull;
+
+import static java.lang.String.format;
+import static java.util.Collections.unmodifiableSet;
+import static java.util.Objects.requireNonNull;
+import static org.sonar.api.web.page.Page.Scope.COMPONENT;
+import static org.sonar.api.web.page.Page.Scope.GLOBAL;
+
+/**
+ * @see PageDefinition
+ * @since 6.3
+ */
+public final class Page {
+ private final String key;
+ private final String name;
+ private final boolean isAdmin;
+ private final Scope scope;
+ private final Set<Qualifier> qualifiers;
+
+ private Page(Builder builder) {
+ this.key = builder.key;
+ this.name = builder.name;
+ this.qualifiers = unmodifiableSet(Stream.of(builder.qualifiers).sorted().collect(Collectors.toSet()));
+ this.isAdmin = builder.isAdmin;
+ this.scope = builder.scope;
+ }
+
+ public static Builder builder(String key) {
+ return new Builder(key);
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Set<Qualifier> getComponentQualifiers() {
+ return qualifiers;
+ }
+
+ public boolean isAdmin() {
+ return isAdmin;
+ }
+
+ public Scope getScope() {
+ return scope;
+ }
+
+ public enum Scope {
+ GLOBAL, COMPONENT
+ }
+
+ public enum Qualifier {
+ PROJECT(org.sonar.api.resources.Qualifiers.PROJECT),
+ MODULE(org.sonar.api.resources.Qualifiers.MODULE),
+ VIEW(org.sonar.api.resources.Qualifiers.VIEW),
+ SUB_VIEW(org.sonar.api.resources.Qualifiers.SUBVIEW);
+
+ private final String key;
+
+ Qualifier(String key) {
+ this.key = key;
+ }
+
+ @CheckForNull
+ public static Qualifier fromKey(String key) {
+ return Arrays.stream(values())
+ .filter(qualifier -> qualifier.key.equals(key))
+ .findAny()
+ .orElse(null);
+ }
+
+ public String getKey() {
+ return key;
+ }
+ }
+
+ public static class Builder {
+ private final String key;
+ private String name;
+ private boolean isAdmin = false;
+ private Scope scope = GLOBAL;
+ private Qualifier[] qualifiers = new Qualifier[] {};
+
+ /**
+ * @param key It must respect the format plugin_key/page_identifier. Example: <code>my_plugin/my_page</code>
+ */
+ private Builder(String key) {
+ this.key = requireNonNull(key, "Key must not be null");
+ }
+
+ /**
+ * Page name displayed in the UI. Mandatory.
+ */
+ public Builder setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * if set to true, display the page in the administration section, depending on the scope
+ */
+ public Builder setAdmin(boolean admin) {
+ this.isAdmin = admin;
+ return this;
+ }
+
+ /**
+ * Define where the page is displayed, either in the global menu or in a component page
+ * @param scope - default is GLOBAL
+ */
+ public Builder setScope(Scope scope) {
+ this.scope = requireNonNull(scope, "Scope must not be null");
+ return this;
+ }
+
+ /**
+ * Define the components where the page is displayed. If set, {@link #setScope(Scope)} must be set to COMPONENT
+ * @see Qualifier
+ */
+ public Builder setComponentQualifiers(Qualifier... qualifiers) {
+ this.qualifiers = requireNonNull(qualifiers);
+ return this;
+ }
+
+ public Page build() {
+ if (key == null || key.isEmpty()) {
+ throw new IllegalArgumentException("Key must not be empty");
+ }
+ if (name == null || name.isEmpty()) {
+ throw new IllegalArgumentException("Name must be defined and not empty");
+ }
+ if (qualifiers.length > 0 && GLOBAL.equals(scope)) {
+ throw new IllegalArgumentException(format("The scope must be '%s' when component qualifiers are provided", COMPONENT));
+ }
+ if (qualifiers.length == 0 && COMPONENT.equals(scope)) {
+ qualifiers = Qualifier.values();
+ }
+
+ return new Page(this);
+ }
+ }
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/web/page/PageDefinition.java b/sonar-plugin-api/src/main/java/org/sonar/api/web/page/PageDefinition.java
new file mode 100644
index 00000000000..3b2d351a2e7
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/web/page/PageDefinition.java
@@ -0,0 +1,77 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.api.web.page;
+
+import org.sonar.api.ExtensionPoint;
+import org.sonar.api.server.ServerSide;
+
+/**
+ * Defines the Javascript pages added to SonarQube.
+ * <br>
+ * This interface replaces the deprecated class {@link org.sonar.api.web.Page}. Moreover, the technology changed from Ruby to Javascript
+ * <br>
+ * <h3>How to define pages</h3>
+ * <pre>
+ * import org.sonar.api.web.page.Page.Qualifier;
+ *
+ * public class MyPluginPagesDefinition implements PagesDefinition {
+ * {@literal @Override}
+ * public void define(Context context) {
+ * context
+ * // Global page by default
+ * .addPage(Page.builder("my_plugin/global_page").setName("Global Page").build())
+ * // Global admin page
+ * .addPage(Page.builder("my_plugin/global_admin_page").setName("Admin Global Page").setAdmin(true).build())
+ * // Project page
+ * .addPage(Page.builder("my_plugin/project_page").setName("Project Page").setScope(Scope.COMPONENT).setQualifiers(Qualifier.PROJECT).build())
+ * // Admin project or module page
+ * .addPage(Page.builder("my_plugin/project_admin_page").setName("Admin Page for Project or Module").setAdmin(true)
+ * .setScope(Scope.COMPONENT).setQualifiers(Qualifier.PROJECT, Qualifier.MODULE).build())
+ * // Page on all components (see Qualifier class) supported
+ * .addPage(Page.builder("my_plugin/component_page").setName("Component Page").setScope(Scope.COMPONENT).build());
+ * }
+ * }
+ * </pre>
+ * <h3>How to test a page definition</h3>
+ * <pre>
+ * public class PageDefinitionTest {
+ * {@literal @Test}
+ * public void test_page_definition() {
+ * PageDefinition underTest = context -> context.addPage(Page.builder("my_plugin/my_page").setName("My Page").build());
+ * Context context = new Context();
+ *
+ * underTest.define(context);
+ *
+ * assertThat(context.getPages()).extracting(Page::getKey).contains("my_plugin/my_page");
+ * }
+ * </pre>
+ *
+ * @since 6.3
+ */
+
+@ServerSide
+@ExtensionPoint
+public interface PageDefinition {
+ /**
+ * This method is executed when server is started
+ */
+ void define(Context context);
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/web/page/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/api/web/page/package-info.java
new file mode 100644
index 00000000000..b1257e7173a
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/web/page/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.api.web.page;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/web/page/ContextTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/web/page/ContextTest.java
new file mode 100644
index 00000000000..b3c2474d56c
--- /dev/null
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/web/page/ContextTest.java
@@ -0,0 +1,72 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.api.web.page;
+
+import java.util.Collection;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+
+public class ContextTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private Context underTest = new Context();
+
+ private Page page = Page.builder("governance/project_export").setName("Project Export").build();
+
+ @Test
+ public void no_pages_with_the_same_path() {
+ underTest.addPage(page);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Page 'Project Export' cannot be loaded. Another page with key 'governance/project_export' already exists.");
+
+ underTest.addPage(page);
+ }
+
+ @Test
+ public void ordered_by_name() {
+ underTest
+ .addPage(Page.builder("K1").setName("N2").build())
+ .addPage(Page.builder("K2").setName("N3").build())
+ .addPage(Page.builder("K3").setName("N1").build());
+
+ Collection<Page> result = underTest.getPages();
+
+ assertThat(result).extracting(Page::getKey, Page::getName)
+ .containsOnly(
+ tuple("K3", "N1"),
+ tuple("K1", "N2"),
+ tuple("K2", "N3"));
+ }
+
+ @Test
+ public void empty_pages_by_default() {
+ Collection<Page> result = underTest.getPages();
+
+ assertThat(result).isEmpty();
+ }
+
+}
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/web/page/PageDefinitionTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/web/page/PageDefinitionTest.java
new file mode 100644
index 00000000000..f5c3033ff0b
--- /dev/null
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/web/page/PageDefinitionTest.java
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.api.web.page;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Used for the documentation
+ */
+public class PageDefinitionTest {
+ @Test
+ public void test_page_definition() {
+ PageDefinition underTest = context -> context.addPage(Page.builder("my_plugin/my_page").setName("My Page").build());
+ Context context = new Context();
+
+ underTest.define(context);
+
+ assertThat(context.getPages()).extracting(Page::getKey).contains("my_plugin/my_page");
+ }
+
+}
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/web/page/PageTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/web/page/PageTest.java
new file mode 100644
index 00000000000..61a2e70ac9e
--- /dev/null
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/web/page/PageTest.java
@@ -0,0 +1,136 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.api.web.page;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.web.page.Page.Qualifier;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.web.page.Page.Qualifier.MODULE;
+import static org.sonar.api.web.page.Page.Qualifier.PROJECT;
+import static org.sonar.api.web.page.Page.Qualifier.SUB_VIEW;
+import static org.sonar.api.web.page.Page.Qualifier.VIEW;
+import static org.sonar.api.web.page.Page.Scope.COMPONENT;
+import static org.sonar.api.web.page.Page.Scope.GLOBAL;
+
+public class PageTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private Page.Builder underTest = Page.builder("governance/project_dump").setName("Project Dump");
+
+ @Test
+ public void full_test() {
+ Page result = underTest
+ .setComponentQualifiers(PROJECT, MODULE)
+ .setScope(COMPONENT)
+ .setAdmin(true)
+ .build();
+
+ assertThat(result.getKey()).isEqualTo("governance/project_dump");
+ assertThat(result.getName()).isEqualTo("Project Dump");
+ assertThat(result.getComponentQualifiers()).containsOnly(PROJECT, MODULE);
+ assertThat(result.getScope()).isEqualTo(COMPONENT);
+ assertThat(result.isAdmin()).isTrue();
+ }
+
+ @Test
+ public void qualifiers_map_to_key() {
+ assertThat(Qualifier.PROJECT.getKey()).isEqualTo(org.sonar.api.resources.Qualifiers.PROJECT);
+ assertThat(Qualifier.MODULE.getKey()).isEqualTo(org.sonar.api.resources.Qualifiers.MODULE);
+ assertThat(Qualifier.VIEW.getKey()).isEqualTo(org.sonar.api.resources.Qualifiers.VIEW);
+ assertThat(Qualifier.SUB_VIEW.getKey()).isEqualTo(org.sonar.api.resources.Qualifiers.SUBVIEW);
+ }
+
+ @Test
+ public void authorized_qualifiers() {
+ Qualifier[] qualifiers = Qualifier.values();
+
+ assertThat(qualifiers).hasSize(4).containsOnly(PROJECT, MODULE, VIEW, SUB_VIEW);
+ }
+
+ @Test
+ public void default_values() {
+ Page result = underTest.build();
+
+ assertThat(result.getComponentQualifiers()).isEmpty();
+ assertThat(result.getScope()).isEqualTo(GLOBAL);
+ assertThat(result.isAdmin()).isFalse();
+ }
+
+ @Test
+ public void all_qualifiers_when_component_page() {
+ Page result = underTest.setScope(COMPONENT).build();
+
+ assertThat(result.getComponentQualifiers()).containsOnly(Qualifier.values());
+ }
+
+ @Test
+ public void qualifiers_from_key() {
+ assertThat(Qualifier.fromKey(Qualifiers.PROJECT)).isEqualTo(Qualifier.PROJECT);
+ assertThat(Qualifier.fromKey("42")).isNull();
+ }
+
+ @Test
+ public void fail_if_no_qualifier() {
+ expectedException.expect(NullPointerException.class);
+
+ underTest.setComponentQualifiers(null).build();
+ }
+
+ @Test
+ public void fail_if_a_page_has_a_null_key() {
+ expectedException.expect(NullPointerException.class);
+
+ Page.builder(null).setName("Say my name").build();
+ }
+
+ @Test
+ public void fail_if_a_page_has_an_empty_key() {
+ expectedException.expect(IllegalArgumentException.class);
+
+ Page.builder("").setName("Say my name").build();
+ }
+
+ @Test
+ public void fail_if_a_page_has_a_null_name() {
+ expectedException.expect(IllegalArgumentException.class);
+
+ Page.builder("governance/project_dump").build();
+ }
+
+ @Test
+ public void fail_if_a_page_has_an_empty_name() {
+ expectedException.expect(IllegalArgumentException.class);
+
+ Page.builder("governance/project_dump").setName("").build();
+ }
+
+ @Test
+ public void fail_if_qualifiers_without_scope() {
+ expectedException.expect(IllegalArgumentException.class);
+
+ underTest.setComponentQualifiers(PROJECT).build();
+ }
+}