diff options
author | Teryk Bellahsene <teryk.bellahsene@sonarsource.com> | 2017-01-06 17:06:01 +0100 |
---|---|---|
committer | Teryk Bellahsene <teryk.bellahsene@sonarsource.com> | 2017-01-13 17:58:12 +0100 |
commit | fb278bdfc64f015f74b757caf37c902ccb50ec67 (patch) | |
tree | fa2e33b8b702ef970d235a922f68b20014b106ba /sonar-plugin-api/src | |
parent | c2d588522c2d44c1d274ee506e56b97c8d087fb4 (diff) | |
download | sonarqube-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')
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(); + } +} |