From faadd654f06e3661fd6c2dfa0708cfe96818f53d Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lievremont Date: Tue, 21 Apr 2015 12:15:11 +0200 Subject: [PATCH] SONAR-6428 WS to get component navigation information --- .../measure/persistence/MeasureDao.java | 5 + .../server/platform/ServerComponents.java | 4 + .../main/java/org/sonar/server/ui/Views.java | 18 +- .../org/sonar/server/ui/package-info.java | 24 + .../ui/ws/ComponentConfigurationPages.java | 156 +++++++ .../ui/ws/ComponentNavigationAction.java | 227 ++++++++++ .../org/sonar/server/ui/ws/package-info.java | 24 + .../sonar/server/ui/ws/example-component.json | 39 ++ .../ws/ComponentConfigurationPagesTest.java | 123 +++++ .../ui/ws/ComponentNavigationActionTest.java | 428 ++++++++++++++++++ .../breadcrumbs.json | 31 ++ .../no_snapshot.json | 16 + .../no_snapshot_user_favourite.json | 16 + .../quality_profile_admin.json | 22 + .../with_admin_rights.json | 30 ++ .../with_dashboards.json | 20 + .../with_extensions.json | 26 ++ .../with_snapshot_and_connected_user.json | 18 + .../core/dashboard/ActiveDashboardDao.java | 13 + .../core/dashboard/ActiveDashboardMapper.java | 2 + .../sonar/core/measure/db/MeasureMapper.java | 2 + .../core/dashboard/ActiveDashboardMapper.xml | 17 + .../sonar/core/measure/db/MeasureMapper.xml | 7 + .../dashboard/ActiveDashboardDaoTest.java | 14 + ...uldSelectProjectDashboardsForAnonymous.xml | 68 +++ .../shouldSelectProjectDashboardsForUser.xml | 85 ++++ .../sonar/api/resources/ResourceTypes.java | 4 + 27 files changed, 1432 insertions(+), 7 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/ui/package-info.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/ui/ws/ComponentConfigurationPages.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/ui/ws/ComponentNavigationAction.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/ui/ws/package-info.java create mode 100644 server/sonar-server/src/main/resources/org/sonar/server/ui/ws/example-component.json create mode 100644 server/sonar-server/src/test/java/org/sonar/server/ui/ws/ComponentConfigurationPagesTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/ui/ws/ComponentNavigationActionTest.java create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/breadcrumbs.json create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/no_snapshot.json create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/no_snapshot_user_favourite.json create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/quality_profile_admin.json create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/with_admin_rights.json create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/with_dashboards.json create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/with_extensions.json create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/with_snapshot_and_connected_user.json create mode 100644 sonar-core/src/test/resources/org/sonar/core/dashboard/ActiveDashboardDaoTest/shouldSelectProjectDashboardsForAnonymous.xml create mode 100644 sonar-core/src/test/resources/org/sonar/core/dashboard/ActiveDashboardDaoTest/shouldSelectProjectDashboardsForUser.xml diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/persistence/MeasureDao.java b/server/sonar-server/src/main/java/org/sonar/server/measure/persistence/MeasureDao.java index 7b1b40db23d..9e0f38d405e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/persistence/MeasureDao.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/persistence/MeasureDao.java @@ -29,6 +29,7 @@ import org.sonar.core.persistence.DaoUtils; import org.sonar.core.persistence.DbSession; import javax.annotation.CheckForNull; + import java.util.List; public class MeasureDao implements ServerComponent, DaoComponent { @@ -55,6 +56,10 @@ public class MeasureDao implements ServerComponent, DaoComponent { mapper(session).insert(measureDto); } + public List selectMetricKeysForSnapshot(DbSession session, Long snapshotId) { + return mapper(session).selectMetricKeysForSnapshot(snapshotId); + } + private MeasureMapper mapper(DbSession session) { return session.getMapper(MeasureMapper.class); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java index b0e7accb761..e455d67390a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java @@ -354,6 +354,8 @@ import org.sonar.server.text.RubyTextService; import org.sonar.server.ui.JRubyI18n; import org.sonar.server.ui.PageDecorations; import org.sonar.server.ui.Views; +import org.sonar.server.ui.ws.ComponentConfigurationPages; +import org.sonar.server.ui.ws.ComponentNavigationAction; import org.sonar.server.ui.ws.GlobalNavigationAction; import org.sonar.server.ui.ws.NavigationWs; import org.sonar.server.ui.ws.SettingsNavigationAction; @@ -923,6 +925,8 @@ class ServerComponents { // UI pico.addSingleton(GlobalNavigationAction.class); pico.addSingleton(SettingsNavigationAction.class); + pico.addSingleton(ComponentConfigurationPages.class); + pico.addSingleton(ComponentNavigationAction.class); pico.addSingleton(NavigationWs.class); for (Object components : level4AddedComponents) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/ui/Views.java b/server/sonar-server/src/main/java/org/sonar/server/ui/Views.java index bf9201934e2..a66890c29ce 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ui/Views.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ui/Views.java @@ -28,6 +28,8 @@ import org.sonar.api.web.Page; import org.sonar.api.web.View; import org.sonar.api.web.Widget; +import javax.annotation.Nullable; + import java.util.List; import java.util.Map; import java.util.Set; @@ -70,7 +72,8 @@ public class Views implements ServerComponent { return getPages(section, null, null, null, null); } - public List> getPages(String section, String resourceScope, String resourceQualifier, String resourceLanguage, String[] availableMeasures) { + public List> getPages(String section, + @Nullable String resourceScope, @Nullable String resourceQualifier, @Nullable String resourceLanguage, @Nullable String[] availableMeasures) { List> result = Lists.newArrayList(); for (ViewProxy proxy : pages) { if (accept(proxy, section, resourceScope, resourceQualifier, resourceLanguage, availableMeasures)) { @@ -109,7 +112,8 @@ public class Views implements ServerComponent { return Lists.newArrayList(widgets); } - protected static boolean accept(ViewProxy proxy, String section, String resourceScope, String resourceQualifier, String resourceLanguage, String[] availableMeasures) { + protected static boolean accept(ViewProxy proxy, + @Nullable String section, @Nullable String resourceScope, @Nullable String resourceQualifier, @Nullable String resourceLanguage, @Nullable String[] availableMeasures) { return acceptNavigationSection(proxy, section) && acceptResourceScope(proxy, resourceScope) && acceptResourceQualifier(proxy, resourceQualifier) @@ -117,23 +121,23 @@ public class Views implements ServerComponent { && acceptAvailableMeasures(proxy, availableMeasures); } - protected static boolean acceptResourceLanguage(ViewProxy proxy, String resourceLanguage) { + protected static boolean acceptResourceLanguage(ViewProxy proxy, @Nullable String resourceLanguage) { return resourceLanguage == null || ArrayUtils.isEmpty(proxy.getResourceLanguages()) || ArrayUtils.contains(proxy.getResourceLanguages(), resourceLanguage); } - protected static boolean acceptResourceScope(ViewProxy proxy, String resourceScope) { + protected static boolean acceptResourceScope(ViewProxy proxy, @Nullable String resourceScope) { return resourceScope == null || ArrayUtils.isEmpty(proxy.getResourceScopes()) || ArrayUtils.contains(proxy.getResourceScopes(), resourceScope); } - protected static boolean acceptResourceQualifier(ViewProxy proxy, String resourceQualifier) { + protected static boolean acceptResourceQualifier(ViewProxy proxy, @Nullable String resourceQualifier) { return resourceQualifier == null || ArrayUtils.isEmpty(proxy.getResourceQualifiers()) || ArrayUtils.contains(proxy.getResourceQualifiers(), resourceQualifier); } - protected static boolean acceptNavigationSection(ViewProxy proxy, String section) { + protected static boolean acceptNavigationSection(ViewProxy proxy, @Nullable String section) { return proxy.isWidget() || section == null || ArrayUtils.contains(proxy.getSections(), section); } - protected static boolean acceptAvailableMeasures(ViewProxy proxy, String[] availableMeasures) { + protected static boolean acceptAvailableMeasures(ViewProxy proxy, @Nullable String[] availableMeasures) { return availableMeasures == null || proxy.acceptsAvailableMeasures(availableMeasures); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/ui/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/ui/package-info.java new file mode 100644 index 00000000000..bdd87834a6f --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/ui/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.server.ui; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-server/src/main/java/org/sonar/server/ui/ws/ComponentConfigurationPages.java b/server/sonar-server/src/main/java/org/sonar/server/ui/ws/ComponentConfigurationPages.java new file mode 100644 index 00000000000..4f0edc62578 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/ui/ws/ComponentConfigurationPages.java @@ -0,0 +1,156 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.server.ui.ws; + +import com.google.common.base.Charsets; +import com.google.common.collect.Lists; +import org.sonar.api.ServerComponent; +import org.sonar.api.i18n.I18n; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.resources.ResourceType; +import org.sonar.api.resources.ResourceTypes; +import org.sonar.api.utils.text.JsonWriter; +import org.sonar.api.web.UserRole; +import org.sonar.core.component.ComponentDto; +import org.sonar.server.user.UserSession; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.List; +import java.util.Locale; + +public class ComponentConfigurationPages implements ServerComponent { + + static final String PROPERTY_COMPARABLE = "comparable"; + static final String PROPERTY_CONFIGURABLE = "configurable"; + static final String PROPERTY_HAS_ROLE_POLICY = "hasRolePolicy"; + static final String PROPERTY_MODIFIABLE_HISTORY = "modifiable_history"; + static final String PROPERTY_UPDATABLE_KEY = "updatable_key"; + static final String PROPERTY_DELETABLE = "deletable"; + + private final I18n i18n; + private final ResourceTypes resourceTypes; + + public ComponentConfigurationPages(I18n i18n, ResourceTypes resourceTypes) { + this.i18n = i18n; + this.resourceTypes = resourceTypes; + } + + List getConfigPages(ComponentDto component, UserSession userSession) { + boolean isAdmin = userSession.hasProjectPermissionByUuid(UserRole.ADMIN, component.projectUuid()); + boolean isProject = Qualifiers.PROJECT.equals(component.qualifier()); + Locale locale = userSession.locale(); + String componentKey = encodeComponentKey(component); + + List configPages = Lists.newArrayList(); + + configPages.add(new ConfigPage( + isAdmin && componentTypeHasProperty(component, PROPERTY_CONFIGURABLE), + String.format("/project/settings?id=%s", componentKey), + i18n.message(locale, "project_settings.page", null))); + + configPages.add(new ConfigPage( + isProject, + String.format("/project/profile?id=%s", componentKey), + i18n.message(locale, "project_quality_profiles.page", null))); + + configPages.add(new ConfigPage( + isProject, + String.format("/project/qualitygate?id=%s", componentKey), + i18n.message(locale, "project_quality_gate.page", null))); + + configPages.add(new ConfigPage( + isAdmin, + String.format("/manual_measures/index?id=%s", componentKey), + i18n.message(locale, "manual_measures.page", null))); + + configPages.add(new ConfigPage( + isAdmin && isProject, + String.format("/action_plans/index?id=%s", componentKey), + i18n.message(locale, "action_plans.page", null))); + + configPages.add(new ConfigPage( + isAdmin && isProject, + String.format("/project/links?id=%s", componentKey), + i18n.message(locale, "action_plans.page", null))); + + configPages.add(new ConfigPage( + componentTypeHasProperty(component, PROPERTY_HAS_ROLE_POLICY), + String.format("/project_roles/index?id=%s", componentKey), + i18n.message(locale, "permissions.page", null))); + + configPages.add(new ConfigPage( + componentTypeHasProperty(component, PROPERTY_MODIFIABLE_HISTORY), + String.format("/project/history?id=%s", componentKey), + i18n.message(locale, "project_history.page", null))); + + configPages.add(new ConfigPage( + componentTypeHasProperty(component, PROPERTY_UPDATABLE_KEY), + String.format("/project/key?id=%s", componentKey), + i18n.message(locale, "update_key.page", null))); + + configPages.add(new ConfigPage( + componentTypeHasProperty(component, PROPERTY_DELETABLE), + String.format("/project/deletion?id=%s", componentKey), + i18n.message(locale, "deletion.page", null))); + + return configPages; + } + + static String encodeComponentKey(ComponentDto component) { + String componentKey = component.getKey(); + try { + componentKey = URLEncoder.encode(componentKey, Charsets.UTF_8.name()); + } catch (UnsupportedEncodingException unknownEncoding) { + throw new IllegalStateException(unknownEncoding); + } + return componentKey; + } + + boolean componentTypeHasProperty(ComponentDto component, String resourceTypeProperty) { + ResourceType resourceType = resourceTypes.get(component.qualifier()); + if (resourceType != null) { + return resourceType.getBooleanProperty(resourceTypeProperty); + } + return false; + } + + static class ConfigPage { + private final boolean visible; + private final String url; + private final String name; + + ConfigPage(boolean visible, String url, String name) { + this.visible = visible; + this.url = url; + this.name = name; + } + + void write(JsonWriter json) { + if (visible) { + json.beginObject() + .prop("url", url) + .prop("name", name) + .endObject(); + } + } + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/ui/ws/ComponentNavigationAction.java b/server/sonar-server/src/main/java/org/sonar/server/ui/ws/ComponentNavigationAction.java new file mode 100644 index 00000000000..8da3efa9455 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/ui/ws/ComponentNavigationAction.java @@ -0,0 +1,227 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.server.ui.ws; + +import com.google.common.collect.Lists; +import org.sonar.api.i18n.I18n; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService.NewAction; +import org.sonar.api.server.ws.WebService.NewController; +import org.sonar.api.utils.DateUtils; +import org.sonar.api.utils.text.JsonWriter; +import org.sonar.api.web.NavigationSection; +import org.sonar.api.web.Page; +import org.sonar.api.web.UserRole; +import org.sonar.core.component.ComponentDto; +import org.sonar.core.component.SnapshotDto; +import org.sonar.core.dashboard.ActiveDashboardDao; +import org.sonar.core.dashboard.DashboardDto; +import org.sonar.core.permission.GlobalPermissions; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.properties.PropertyDto; +import org.sonar.core.properties.PropertyQuery; +import org.sonar.server.db.DbClient; +import org.sonar.server.ui.ViewProxy; +import org.sonar.server.ui.Views; +import org.sonar.server.ui.ws.ComponentConfigurationPages.ConfigPage; +import org.sonar.server.user.UserSession; + +import javax.annotation.Nullable; + +import java.util.Date; +import java.util.List; +import java.util.Locale; + +public class ComponentNavigationAction implements NavigationAction { + + private static final String PARAM_COMPONENT_KEY = "componentKey"; + + private final DbClient dbClient; + private final ActiveDashboardDao activeDashboardDao; + private final Views views; + private final I18n i18n; + private final ComponentConfigurationPages projectConfiguration; + + public ComponentNavigationAction(DbClient dbClient, ActiveDashboardDao activeDashboardDao, Views views, I18n i18n, + ComponentConfigurationPages projectConfiguration) { + this.dbClient = dbClient; + this.activeDashboardDao = activeDashboardDao; + this.views = views; + this.i18n = i18n; + this.projectConfiguration = projectConfiguration; + } + + @Override + public void define(NewController context) { + NewAction projectNavigation = context.createAction("component") + .setDescription("Get information concerning component navigation for the current user. " + + "Requires the 'Browse' permission on the component's project.") + .setHandler(this) + .setInternal(true) + .setResponseExample(getClass().getResource("example-component.json")) + .setSince("5.2"); + + projectNavigation.createParam(PARAM_COMPONENT_KEY) + .setDescription("A component key.") + .setExampleValue("org.codehaus.sonar:sonar") + .setRequired(true); + } + + @Override + public void handle(Request request, Response response) throws Exception { + String componentKey = request.mandatoryParam(PARAM_COMPONENT_KEY); + + UserSession userSession = UserSession.get(); + DbSession session = dbClient.openSession(false); + + try { + ComponentDto component = dbClient.componentDao().getByKey(session, componentKey); + + userSession.checkProjectUuidPermission(UserRole.USER, component.projectUuid()); + + SnapshotDto snapshot = dbClient.snapshotDao().getLastSnapshot(session, new SnapshotDto().setResourceId(component.getId())); + + JsonWriter json = response.newJsonWriter(); + json.beginObject(); + writeComponent(json, session, component, snapshot, userSession); + + if (userSession.hasProjectPermissionByUuid(UserRole.ADMIN, component.projectUuid()) || userSession.hasGlobalPermission(GlobalPermissions.QUALITY_PROFILE_ADMIN)) { + writeConfiguration(json, component, userSession); + } + + writeBreadCrumbs(json, session, component, snapshot); + json.endObject().close(); + + } finally { + session.close(); + } + } + + private void writeComponent(JsonWriter json, DbSession session, ComponentDto component, @Nullable SnapshotDto snapshot, UserSession userSession) { + + json.prop("key", component.key()) + .prop("uuid", component.uuid()) + .prop("name", component.name()) + .prop("isComparable", projectConfiguration.componentTypeHasProperty(component, ComponentConfigurationPages.PROPERTY_COMPARABLE)) + .prop("canBeFavorite", userSession.isLoggedIn()) + .prop("isFavorite", isFavourite(session, component, userSession)); + + List dashboards = activeDashboardDao.selectProjectDashboardsForUserLogin(session, userSession.login()); + writeDashboards(json, component, dashboards, userSession.locale()); + + if (snapshot != null) { + json.prop("version", snapshot.getVersion()) + .prop("date", DateUtils.formatDateTime(new Date(snapshot.getCreatedAt()))); + String[] availableMeasures = dbClient.measureDao().selectMetricKeysForSnapshot(session, snapshot.getId()).toArray(new String[0]); + List> pages = views.getPages(NavigationSection.RESOURCE, component.scope(), component.qualifier(), component.language(), availableMeasures); + writeExtensions(json, component, pages, userSession.locale()); + } + } + + private boolean isFavourite(DbSession session, ComponentDto component, UserSession userSession) { + PropertyQuery propertyQuery = PropertyQuery.builder() + .setUserId(userSession.userId()) + .setKey("favourite") + .setComponentId(component.getId()) + .build(); + List componentFavourites = dbClient.propertiesDao().selectByQuery(propertyQuery, session); + return componentFavourites.size() == 1; + } + + private void writeExtensions(JsonWriter json, ComponentDto component, List> pages, Locale locale) { + json.name("extensions").beginArray(); + for (ViewProxy page: pages) { + writePage(json, getPageUrl(page, component), i18n.message(locale, page.getId() + ".page", page.getTitle())); + } + json.endArray(); + } + + private String getPageUrl(ViewProxy page, ComponentDto component) { + String result = null; + String componentKey = ComponentConfigurationPages.encodeComponentKey(component); + if (page.isController()) { + result = String.format("%s?id=%s", page.getId(), componentKey); + } else { + result = String.format("/plugins/resource/%s?page=%s", componentKey, page.getId()); + } + return result; + } + + private void writeDashboards(JsonWriter json, ComponentDto component, List dashboards, Locale locale) { + json.name("dashboards").beginArray(); + for (DashboardDto dashboard : dashboards) { + json.beginObject() + .prop("key", dashboard.getId()) + .prop("name", i18n.message(locale, String.format("dashboard.%s.name", dashboard.getName()), dashboard.getName())) + .endObject(); + } + json.endArray(); + } + + private void writeConfiguration(JsonWriter json, ComponentDto component, UserSession userSession) { + boolean isAdmin = userSession.hasProjectPermissionByUuid(UserRole.ADMIN, component.projectUuid()); + Locale locale = userSession.locale(); + + json.name("configuration").beginArray(); + for (ConfigPage page : projectConfiguration.getConfigPages(component, userSession)) { + page.write(json); + } + + if (isAdmin) { + List> configPages = views.getPages(NavigationSection.RESOURCE_CONFIGURATION, component.scope(), component.qualifier(), component.language(), null); + for (ViewProxy page : configPages) { + writePage(json, getPageUrl(page, component), i18n.message(locale, page.getId() + ".page", page.getTitle())); + } + } + json.endArray(); + } + + private void writePage(JsonWriter json, String url, String name) { + json.beginObject() + .prop("url", url) + .prop("name", name) + .endObject(); + } + + private void writeBreadCrumbs(JsonWriter json, DbSession session, ComponentDto component, @Nullable SnapshotDto snapshot) { + json.name("breadcrumbs").beginArray(); + + List componentPath = Lists.newArrayList(component); + + if (snapshot != null) { + SnapshotDto currentSnapshot = snapshot; + while (currentSnapshot.getParentId() != null) { + currentSnapshot = dbClient.snapshotDao().getByKey(session, currentSnapshot.getParentId()); + componentPath.add(0, dbClient.componentDao().getById(currentSnapshot.getResourceId(), session)); + } + } + + for (ComponentDto crumbComponent : componentPath) { + json.beginObject() + .prop("key", crumbComponent.key()) + .prop("name", crumbComponent.name()) + .prop("qualifier", crumbComponent.qualifier()) + .endObject(); + } + + json.endArray(); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/ui/ws/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/ui/ws/package-info.java new file mode 100644 index 00000000000..4f12742a880 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/ui/ws/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.server.ui.ws; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-server/src/main/resources/org/sonar/server/ui/ws/example-component.json b/server/sonar-server/src/main/resources/org/sonar/server/ui/ws/example-component.json new file mode 100644 index 00000000000..b03cef5d95d --- /dev/null +++ b/server/sonar-server/src/main/resources/org/sonar/server/ui/ws/example-component.json @@ -0,0 +1,39 @@ +{ + "id": 2865, + "key": "org.codehaus.sonar:sonar", + "uuid": "69e57151-be0d-4157-adff-c06741d88879", + "isComparable": true, + "canBeFavorite": true, + "isFavorite": true, + "version": "5.2-SNAPSHOT", + "snapshotDate": "2015-04-16T14:40:32+02:00", + "dashboards": [ + { + "key": 1, + "name": "Main Dashboard" + } + ], + "extensions": [ + { + "name": "My Resource Plugin", + "url": "/plugins/resource/2865?page=my-resource-plugin" + } + ], + "configuration": [ + { + "name": "General Settings", + "url": "/project/settings/2865" + }, + { + "name": "Action Plans", + "url": "/action_plans/index/2865" + } + ], + "breadcrumbs": [ + { + "name": "SonarQube", + "qualifier": "TRK", + "key": "org.codehaus.sonar:sonar" + } + ] +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/ui/ws/ComponentConfigurationPagesTest.java b/server/sonar-server/src/test/java/org/sonar/server/ui/ws/ComponentConfigurationPagesTest.java new file mode 100644 index 00000000000..1cfbe7d316b --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/ui/ws/ComponentConfigurationPagesTest.java @@ -0,0 +1,123 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.server.ui.ws; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; +import org.sonar.api.i18n.I18n; +import org.sonar.api.resources.ResourceType; +import org.sonar.api.resources.ResourceTypes; +import org.sonar.api.web.UserRole; +import org.sonar.core.component.ComponentDto; +import org.sonar.server.component.ComponentTesting; +import org.sonar.server.ui.ws.ComponentConfigurationPages.ConfigPage; +import org.sonar.server.user.MockUserSession; +import org.sonar.server.user.UserSession; + +import java.util.List; +import java.util.Locale; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class ComponentConfigurationPagesTest { + + @Mock + private I18n i18n; + + @Mock + private ResourceTypes resourceTypes; + + @Before + public void before() { + when(i18n.message(Matchers.any(Locale.class), Matchers.anyString(), Matchers.anyString())).thenAnswer(new Answer() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + return invocation.getArgumentAt(1, String.class); + } + }); + } + + @Test + public void pages_for_project() throws Exception { + String uuid = "abcd"; + ComponentDto component = ComponentTesting.newProjectDto(uuid).setKey("org.codehaus.sonar:sonar"); + UserSession userSession = MockUserSession.set().setLogin("obiwan").addProjectUuidPermissions(UserRole.ADMIN, uuid); + + List pages = new ComponentConfigurationPages(i18n, resourceTypes).getConfigPages(component, userSession); + assertThat(pages).extracting("visible").containsExactly( + false, true, true, true, true, true, false, false, false, false); + assertThat(pages).extracting("url").containsExactly( + "/project/settings?id=org.codehaus.sonar%3Asonar", + "/project/profile?id=org.codehaus.sonar%3Asonar", + "/project/qualitygate?id=org.codehaus.sonar%3Asonar", + "/manual_measures/index?id=org.codehaus.sonar%3Asonar", + "/action_plans/index?id=org.codehaus.sonar%3Asonar", + "/project/links?id=org.codehaus.sonar%3Asonar", + "/project_roles/index?id=org.codehaus.sonar%3Asonar", + "/project/history?id=org.codehaus.sonar%3Asonar", + "/project/key?id=org.codehaus.sonar%3Asonar", + "/project/deletion?id=org.codehaus.sonar%3Asonar" + ); + } + + @Test + public void pages_for_project_with_resource_type_property() throws Exception { + String uuid = "abcd"; + ComponentDto component = ComponentTesting.newProjectDto(uuid); + UserSession userSession = MockUserSession.set().setLogin("obiwan").addProjectUuidPermissions(UserRole.ADMIN, uuid); + when(resourceTypes.get(component.qualifier())).thenReturn( + ResourceType.builder(component.qualifier()).setProperty("configurable", true).build()); + + List pages = new ComponentConfigurationPages(i18n, resourceTypes).getConfigPages(component, userSession); + assertThat(pages).extracting("visible").containsExactly( + true, true, true, true, true, true, false, false, false, false); + } + + @Test + public void pages_for_module() throws Exception { + String uuid = "abcd"; + ComponentDto project = ComponentTesting.newProjectDto(uuid); + ComponentDto module = ComponentTesting.newModuleDto(project); + UserSession userSession = MockUserSession.set().setLogin("obiwan").addProjectUuidPermissions(UserRole.ADMIN, uuid); + + List pages = new ComponentConfigurationPages(i18n, resourceTypes).getConfigPages(module, userSession); + assertThat(pages).extracting("visible").containsExactly( + false, false, false, true, false, false, false, false, false, false); + } + + @Test + public void pages_for_non_admin() throws Exception { + String uuid = "abcd"; + ComponentDto project = ComponentTesting.newProjectDto(uuid); + UserSession userSession = MockUserSession.set().setLogin("obiwan").addProjectUuidPermissions(UserRole.USER, uuid); + + List pages = new ComponentConfigurationPages(i18n, resourceTypes).getConfigPages(project, userSession); + assertThat(pages).extracting("visible").containsExactly( + false, true, true, false, false, false, false, false, false, false); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/ui/ws/ComponentNavigationActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/ui/ws/ComponentNavigationActionTest.java new file mode 100644 index 00000000000..1479c6ee3b6 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/ui/ws/ComponentNavigationActionTest.java @@ -0,0 +1,428 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.server.ui.ws; + +import com.google.common.collect.Maps; +import org.apache.commons.lang.BooleanUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.sonar.api.i18n.I18n; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.resources.Scopes; +import org.sonar.api.utils.DateUtils; +import org.sonar.api.utils.System2; +import org.sonar.api.web.NavigationSection; +import org.sonar.api.web.Page; +import org.sonar.api.web.ResourceLanguage; +import org.sonar.api.web.ResourceQualifier; +import org.sonar.api.web.ResourceScope; +import org.sonar.api.web.UserRole; +import org.sonar.core.component.ComponentDto; +import org.sonar.core.component.SnapshotDto; +import org.sonar.core.dashboard.ActiveDashboardDao; +import org.sonar.core.dashboard.ActiveDashboardDto; +import org.sonar.core.dashboard.DashboardDao; +import org.sonar.core.dashboard.DashboardDto; +import org.sonar.core.permission.GlobalPermissions; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.persistence.DbTester; +import org.sonar.core.properties.PropertiesDao; +import org.sonar.core.properties.PropertyDto; +import org.sonar.server.component.ComponentTesting; +import org.sonar.server.component.db.ComponentDao; +import org.sonar.server.component.db.SnapshotDao; +import org.sonar.server.db.DbClient; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.measure.persistence.MeasureDao; +import org.sonar.server.ui.Views; +import org.sonar.server.user.MockUserSession; +import org.sonar.server.user.UserSession; +import org.sonar.server.user.db.UserDao; +import org.sonar.server.ws.WsTester; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ComponentNavigationActionTest { + + @ClassRule + public static final DbTester dbTester = new DbTester(); + + private DbSession session; + + private WsTester wsTester; + + private UserDao userDao; + + private DashboardDao dashboardDao; + + private ActiveDashboardDao activeDashboardDao; + + private DbClient dbClient; + + private I18n i18n; + + private ProjectConfigurationPagesStub projectConfigurationPages; + + @Before + public void before() throws Exception { + dbTester.truncateTables(); + + System2 system = mock(System2.class); + userDao = new UserDao(dbTester.myBatis(), system); + dashboardDao = new DashboardDao(dbTester.myBatis()); + activeDashboardDao = new ActiveDashboardDao(dbTester.myBatis()); + dbClient = new DbClient( + dbTester.database(), dbTester.myBatis(), userDao, dashboardDao, activeDashboardDao, + new ComponentDao(system), new SnapshotDao(system), new PropertiesDao(dbTester.myBatis()), + new MeasureDao()); + + i18n = mock(I18n.class); + when(i18n.message(any(Locale.class), any(String.class), any(String.class))) + .thenAnswer(new Answer() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + return invocation.getArgumentAt(2, String.class); + } + }); + + projectConfigurationPages = new ProjectConfigurationPagesStub(); + + session = dbClient.openSession(false); + } + + @After + public void after() throws Exception { + session.close(); + } + + @Test(expected = IllegalArgumentException.class) + public void fail_on_missing_parameters() throws Exception { + wsTester = new WsTester(new NavigationWs(new ComponentNavigationAction(null, null, null, null, null))); + + wsTester.newGetRequest("api/navigation", "component").execute(); + } + + @Test(expected = NotFoundException.class) + public void fail_on_unexistent_key() throws Exception { + wsTester = new WsTester(new NavigationWs(new ComponentNavigationAction(dbClient, null, null, null, null))); + + wsTester.newGetRequest("api/navigation", "component").setParam("componentKey", "polop").execute(); + } + + @Test(expected = ForbiddenException.class) + public void fail_on_missing_permission() throws Exception { + dbClient.componentDao().insert(session, ComponentTesting.newProjectDto("abcd").setKey("polop")); + session.commit(); + + MockUserSession.set(); + wsTester = new WsTester(new NavigationWs(new ComponentNavigationAction(dbClient, null, null, null, null))); + + wsTester.newGetRequest("api/navigation", "component").setParam("componentKey", "polop").execute(); + } + + @Test + public void no_snapshot_anonymous() throws Exception { + dbClient.componentDao().insert(session, ComponentTesting.newProjectDto("abcd") + .setKey("polop").setName("Polop")); + session.commit(); + + MockUserSession.set().addProjectUuidPermissions(UserRole.USER, "abcd"); + wsTester = new WsTester(new NavigationWs(new ComponentNavigationAction(dbClient, activeDashboardDao, + new Views(), i18n, projectConfigurationPages))); + + wsTester.newGetRequest("api/navigation", "component").setParam("componentKey", "polop").execute().assertJson(getClass(), "no_snapshot.json"); + } + + @Test + public void no_snapshot_connected_user_and_favorite() throws Exception { + int userId = 42; + ComponentDto project = dbClient.componentDao().insert(session, ComponentTesting.newProjectDto("abcd") + .setKey("polop").setName("Polop")); + dbClient.propertiesDao().setProperty(new PropertyDto().setKey("favourite").setResourceId(project.getId()).setUserId((long) userId), session); + session.commit(); + + MockUserSession.set().setLogin("obiwan").setUserId(userId).addProjectUuidPermissions(UserRole.USER, "abcd"); + + wsTester = new WsTester(new NavigationWs(new ComponentNavigationAction(dbClient, activeDashboardDao, + new Views(), i18n, projectConfigurationPages))); + + wsTester.newGetRequest("api/navigation", "component").setParam("componentKey", "polop").execute().assertJson(getClass(), "no_snapshot_user_favourite.json"); + } + + @Test + public void with_snapshot_and_connected_user() throws Exception { + Date snapshotDate = DateUtils.parseDateTime("2015-04-22T11:44:00+0200"); + + int userId = 42; + ComponentDto project = dbClient.componentDao().insert(session, ComponentTesting.newProjectDto("abcd") + .setKey("polop").setName("Polop")); + dbClient.snapshotDao().insert(session, new SnapshotDto().setCreatedAt(snapshotDate.getTime()).setVersion("3.14") + .setLast(true).setQualifier(project.qualifier()).setResourceId(project.getId()).setRootProjectId(project.getId()).setScope(project.scope())); + session.commit(); + + MockUserSession.set().setLogin("obiwan").setUserId(userId).addProjectUuidPermissions(UserRole.USER, "abcd"); + + wsTester = new WsTester(new NavigationWs(new ComponentNavigationAction(dbClient, activeDashboardDao, + new Views(), i18n, projectConfigurationPages))); + + wsTester.newGetRequest("api/navigation", "component").setParam("componentKey", "polop").execute().assertJson(getClass(), "with_snapshot_and_connected_user.json"); + } + + @Test + public void with_dashboards() throws Exception { + dbClient.componentDao().insert(session, ComponentTesting.newProjectDto("abcd") + .setKey("polop").setName("Polop")); + DashboardDto dashboard = new DashboardDto().setGlobal(false).setName("Anon Dashboard").setShared(true).setColumnLayout("100%"); + dashboardDao.insert(dashboard); + activeDashboardDao.insert(new ActiveDashboardDto().setDashboardId(dashboard.getId())); + session.commit(); + + MockUserSession.set().addProjectUuidPermissions(UserRole.USER, "abcd"); + + wsTester = new WsTester(new NavigationWs(new ComponentNavigationAction(dbClient, activeDashboardDao, + new Views(), i18n, projectConfigurationPages))); + + wsTester.newGetRequest("api/navigation", "component").setParam("componentKey", "polop").execute().assertJson(getClass(), "with_dashboards.json"); + } + + @Test + public void with_extensions() throws Exception { + final String language = "xoo"; + ComponentDto project = dbClient.componentDao().insert(session, ComponentTesting.newProjectDto("abcd") + .setKey("polop").setName("Polop").setLanguage(language)); + dbClient.snapshotDao().insert(session, new SnapshotDto() + .setLast(true).setQualifier(project.qualifier()).setResourceId(project.getId()).setRootProjectId(project.getId()).setScope(project.scope())); + session.commit(); + + MockUserSession.set().addProjectUuidPermissions(UserRole.USER, "abcd"); + + @NavigationSection(NavigationSection.RESOURCE) + @ResourceScope(Scopes.PROJECT) + @ResourceQualifier(Qualifiers.PROJECT) + @ResourceLanguage(language) + class FirstPage implements Page { + @Override + public String getTitle() { + return "First Page"; + } + + @Override + public String getId() { + return "first_page"; + } + } + Page page1 = new FirstPage(); + + @NavigationSection(NavigationSection.RESOURCE) + @ResourceScope(Scopes.PROJECT) + @ResourceQualifier(Qualifiers.PROJECT) + @ResourceLanguage(language) + class SecondPage implements Page { + @Override + public String getTitle() { + return "Second Page"; + } + + @Override + public String getId() { + return "/second/page"; + } + } + Page page2 = new SecondPage(); + + wsTester = new WsTester(new NavigationWs(new ComponentNavigationAction(dbClient, activeDashboardDao, + new Views(new Page[] {page1, page2}), i18n, projectConfigurationPages))); + + wsTester.newGetRequest("api/navigation", "component").setParam("componentKey", "polop").execute().assertJson(getClass(), "with_extensions.json"); + } + + @Test + public void with_admin_rights() throws Exception { + final String language = "xoo"; + int userId = 42; + dbClient.componentDao().insert(session, ComponentTesting.newProjectDto("abcd") + .setKey("polop").setName("Polop").setLanguage(language)); + session.commit(); + + MockUserSession.set().setLogin("obiwan").setUserId(userId) + .addProjectUuidPermissions(UserRole.USER, "abcd") + .addProjectUuidPermissions(UserRole.ADMIN, "abcd"); + + @NavigationSection(NavigationSection.RESOURCE_CONFIGURATION) + @ResourceScope(Scopes.PROJECT) + @ResourceQualifier(Qualifiers.PROJECT) + @ResourceLanguage(language) + class FirstPage implements Page { + @Override + public String getTitle() { + return "First Page"; + } + + @Override + public String getId() { + return "first_page"; + } + } + Page page1 = new FirstPage(); + + @NavigationSection(NavigationSection.RESOURCE_CONFIGURATION) + @ResourceScope(Scopes.PROJECT) + @ResourceQualifier(Qualifiers.PROJECT) + @ResourceLanguage(language) + class SecondPage implements Page { + @Override + public String getTitle() { + return "Second Page"; + } + + @Override + public String getId() { + return "/second/page"; + } + } + Page page2 = new SecondPage(); + + wsTester = new WsTester(new NavigationWs(new ComponentNavigationAction(dbClient, activeDashboardDao, + new Views(new Page[] {page1, page2}), i18n, projectConfigurationPages))); + + wsTester.newGetRequest("api/navigation", "component").setParam("componentKey", "polop").execute().assertJson(getClass(), "with_admin_rights.json"); + } + + @Test + public void with_quality_profile_admin_rights() throws Exception { + final String language = "xoo"; + int userId = 42; + dbClient.componentDao().insert(session, ComponentTesting.newProjectDto("abcd") + .setKey("polop").setName("Polop").setLanguage(language)); + session.commit(); + + MockUserSession.set().setLogin("obiwan").setUserId(userId) + .addProjectUuidPermissions(UserRole.USER, "abcd") + .setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN); + + @NavigationSection(NavigationSection.RESOURCE_CONFIGURATION) + class FirstPage implements Page { + @Override + public String getTitle() { + return "First Page"; + } + + @Override + public String getId() { + return "first_page"; + } + } + Page page = new FirstPage(); + + wsTester = new WsTester(new NavigationWs(new ComponentNavigationAction(dbClient, activeDashboardDao, + new Views(new Page[] {page}), i18n, projectConfigurationPages))); + + wsTester.newGetRequest("api/navigation", "component").setParam("componentKey", "polop").execute().assertJson(getClass(), "quality_profile_admin.json"); + } + + @Test + public void bread_crumbs_on_several_levels() throws Exception { + ComponentDto project = ComponentTesting.newProjectDto("abcd") + .setKey("polop").setName("Polop"); + ComponentDto module = ComponentTesting.newModuleDto("bcde", project) + .setKey("palap").setName("Palap"); + ComponentDto directory = ComponentTesting.newDirectory(module, "src/main/xoo"); + ComponentDto file = ComponentTesting.newFileDto(module, "cdef").setName("Source.xoo") + .setKey("palap:src/main/xoo/Source.xoo") + .setPath(directory.path()); + dbClient.componentDao().insert(session, project, module, directory, file); + + SnapshotDto projectSnapshot = dbClient.snapshotDao().insert(session, new SnapshotDto() + .setLast(true) + .setQualifier(project.qualifier()) + .setResourceId(project.getId()) + .setRootProjectId(project.getId()) + .setScope(project.scope())); + SnapshotDto moduleSnapshot = dbClient.snapshotDao().insert(session, new SnapshotDto() + .setLast(true) + .setQualifier(module.qualifier()) + .setResourceId(module.getId()) + .setRootProjectId(project.getId()) + .setScope(module.scope()) + .setParentId(projectSnapshot.getId())); + SnapshotDto directorySnapshot = dbClient.snapshotDao().insert(session, new SnapshotDto() + .setLast(true) + .setQualifier(directory.qualifier()) + .setResourceId(directory.getId()) + .setRootProjectId(project.getId()) + .setScope(directory.scope()) + .setParentId(moduleSnapshot.getId())); + dbClient.snapshotDao().insert(session, new SnapshotDto() + .setLast(true) + .setQualifier(file.qualifier()) + .setResourceId(file.getId()) + .setRootProjectId(project.getId()) + .setScope(file.scope()) + .setParentId(directorySnapshot.getId())); + + session.commit(); + + MockUserSession.set().addProjectUuidPermissions(UserRole.USER, "abcd"); + + wsTester = new WsTester(new NavigationWs(new ComponentNavigationAction(dbClient, activeDashboardDao, + new Views(), i18n, projectConfigurationPages))); + + wsTester.newGetRequest("api/navigation", "component").setParam("componentKey", "palap:src/main/xoo/Source.xoo").execute().assertJson(getClass(), "breadcrumbs.json"); + } + + class ProjectConfigurationPagesStub extends ComponentConfigurationPages { + + private Map resourceTypeHasProperty; + + public ProjectConfigurationPagesStub() { + super(i18n, null); + resourceTypeHasProperty = Maps.newHashMap(); + } + + void setComponentTypeProperty(String resourceTypeProperty, boolean value) { + resourceTypeHasProperty.put(resourceTypeProperty, value); + } + + @Override + boolean componentTypeHasProperty(ComponentDto component, String resourceTypeProperty) { + return BooleanUtils.isTrue(resourceTypeHasProperty.get(resourceTypeProperty)); + } + + @Override + List getConfigPages(ComponentDto component, UserSession userSession) { + return Arrays.asList( + new ConfigPage(true, "/visible/page", "Visible Config Page"), + new ConfigPage(false, "/invisible/page", "Invisible Config Page")); + } + } +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/breadcrumbs.json b/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/breadcrumbs.json new file mode 100644 index 00000000000..61593d8cacd --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/breadcrumbs.json @@ -0,0 +1,31 @@ +{ + "key": "palap:src/main/xoo/Source.xoo", + "uuid": "cdef", + "name": "Source.xoo", + "isComparable": false, + "canBeFavorite": false, + "isFavorite": false, + "dashboards": [], + "breadcrumbs": [ + { + "key": "polop", + "name": "Polop", + "qualifier": "TRK" + }, + { + "key": "palap", + "name": "Palap", + "qualifier": "BRC" + }, + { + "key": "palap:src/main/xoo", + "name": "src/main/xoo", + "qualifier": "DIR" + }, + { + "key": "palap:src/main/xoo/Source.xoo", + "name": "Source.xoo", + "qualifier": "FIL" + } + ] +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/no_snapshot.json b/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/no_snapshot.json new file mode 100644 index 00000000000..0aa0d5825e6 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/no_snapshot.json @@ -0,0 +1,16 @@ +{ + "key": "polop", + "uuid": "abcd", + "name": "Polop", + "isComparable": false, + "canBeFavorite": false, + "isFavorite": false, + "dashboards": [], + "breadcrumbs": [ + { + "key": "polop", + "name": "Polop", + "qualifier": "TRK" + } + ] +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/no_snapshot_user_favourite.json b/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/no_snapshot_user_favourite.json new file mode 100644 index 00000000000..1f3d471aab6 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/no_snapshot_user_favourite.json @@ -0,0 +1,16 @@ +{ + "key": "polop", + "uuid": "abcd", + "name": "Polop", + "isComparable": false, + "canBeFavorite": true, + "isFavorite": true, + "dashboards": [], + "breadcrumbs": [ + { + "key": "polop", + "name": "Polop", + "qualifier": "TRK" + } + ] +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/quality_profile_admin.json b/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/quality_profile_admin.json new file mode 100644 index 00000000000..c377a7b5fa3 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/quality_profile_admin.json @@ -0,0 +1,22 @@ +{ + "key": "polop", + "uuid": "abcd", + "name": "Polop", + "isComparable": false, + "canBeFavorite": true, + "isFavorite": false, + "dashboards": [], + "configuration": [ + { + "name": "Visible Config Page", + "url": "/visible/page" + } + ], + "breadcrumbs": [ + { + "key": "polop", + "name": "Polop", + "qualifier": "TRK" + } + ] +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/with_admin_rights.json b/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/with_admin_rights.json new file mode 100644 index 00000000000..09726b6186b --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/with_admin_rights.json @@ -0,0 +1,30 @@ +{ + "key": "polop", + "uuid": "abcd", + "name": "Polop", + "isComparable": false, + "canBeFavorite": true, + "isFavorite": false, + "dashboards": [], + "configuration": [ + { + "name": "Visible Config Page", + "url": "/visible/page" + }, + { + "name": "First Page", + "url": "/plugins/resource/polop?page=first_page" + }, + { + "name": "Second Page", + "url": "/second/page?id=polop" + } + ], + "breadcrumbs": [ + { + "key": "polop", + "name": "Polop", + "qualifier": "TRK" + } + ] +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/with_dashboards.json b/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/with_dashboards.json new file mode 100644 index 00000000000..2d54fcd3085 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/with_dashboards.json @@ -0,0 +1,20 @@ +{ + "key": "polop", + "uuid": "abcd", + "name": "Polop", + "isComparable": false, + "canBeFavorite": false, + "isFavorite": false, + "dashboards": [ + { + "name": "Anon Dashboard" + } + ], + "breadcrumbs": [ + { + "key": "polop", + "name": "Polop", + "qualifier": "TRK" + } + ] +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/with_extensions.json b/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/with_extensions.json new file mode 100644 index 00000000000..9f3096391a8 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/with_extensions.json @@ -0,0 +1,26 @@ +{ + "key": "polop", + "uuid": "abcd", + "name": "Polop", + "isComparable": false, + "canBeFavorite": false, + "isFavorite": false, + "dashboards": [], + "extensions": [ + { + "name": "First Page", + "url": "/plugins/resource/polop?page=first_page" + }, + { + "name": "Second Page", + "url": "/second/page?id=polop" + } + ], + "breadcrumbs": [ + { + "key": "polop", + "name": "Polop", + "qualifier": "TRK" + } + ] +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/with_snapshot_and_connected_user.json b/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/with_snapshot_and_connected_user.json new file mode 100644 index 00000000000..1c55fce7b66 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentNavigationActionTest/with_snapshot_and_connected_user.json @@ -0,0 +1,18 @@ +{ + "key": "polop", + "uuid": "abcd", + "name": "Polop", + "isComparable": false, + "canBeFavorite": true, + "isFavorite": false, + "date": "2015-04-22T11:44:00+0200", + "version": "3.14", + "dashboards": [], + "breadcrumbs": [ + { + "key": "polop", + "name": "Polop", + "qualifier": "TRK" + } + ] +} diff --git a/sonar-core/src/main/java/org/sonar/core/dashboard/ActiveDashboardDao.java b/sonar-core/src/main/java/org/sonar/core/dashboard/ActiveDashboardDao.java index cc3de408fc6..de705d74eae 100644 --- a/sonar-core/src/main/java/org/sonar/core/dashboard/ActiveDashboardDao.java +++ b/sonar-core/src/main/java/org/sonar/core/dashboard/ActiveDashboardDao.java @@ -67,6 +67,19 @@ public class ActiveDashboardDao implements BatchComponent, ServerComponent, DaoC } } + public List selectProjectDashboardsForUserLogin(@Nullable String login) { + SqlSession session = mybatis.openSession(false); + try { + return selectProjectDashboardsForUserLogin(session, login); + } finally { + session.close(); + } + } + + public List selectProjectDashboardsForUserLogin(SqlSession session, @Nullable String login) { + return getMapper(session).selectProjectDashboardsForUserLogin(login); + } + private ActiveDashboardMapper getMapper(SqlSession session) { return session.getMapper(ActiveDashboardMapper.class); } diff --git a/sonar-core/src/main/java/org/sonar/core/dashboard/ActiveDashboardMapper.java b/sonar-core/src/main/java/org/sonar/core/dashboard/ActiveDashboardMapper.java index 42dbc93c8aa..b7f951a9a94 100644 --- a/sonar-core/src/main/java/org/sonar/core/dashboard/ActiveDashboardMapper.java +++ b/sonar-core/src/main/java/org/sonar/core/dashboard/ActiveDashboardMapper.java @@ -35,4 +35,6 @@ public interface ActiveDashboardMapper { Integer selectMaxOrderIndexForNullUser(); List selectGlobalDashboardsForUserLogin(@Nullable @Param("login") String login); + + List selectProjectDashboardsForUserLogin(@Nullable @Param("login") String login); } diff --git a/sonar-core/src/main/java/org/sonar/core/measure/db/MeasureMapper.java b/sonar-core/src/main/java/org/sonar/core/measure/db/MeasureMapper.java index de8df080ea6..39a32c758ae 100644 --- a/sonar-core/src/main/java/org/sonar/core/measure/db/MeasureMapper.java +++ b/sonar-core/src/main/java/org/sonar/core/measure/db/MeasureMapper.java @@ -38,4 +38,6 @@ public interface MeasureMapper { long countByComponentAndMetric(@Param("componentKey") String componentKey, @Param("metricKey") String metricKey); void insert(MeasureDto measureDto); + + List selectMetricKeysForSnapshot(@Param("snapshotId") long snapshotId); } diff --git a/sonar-core/src/main/resources/org/sonar/core/dashboard/ActiveDashboardMapper.xml b/sonar-core/src/main/resources/org/sonar/core/dashboard/ActiveDashboardMapper.xml index 68b740e858c..a192aab847f 100644 --- a/sonar-core/src/main/resources/org/sonar/core/dashboard/ActiveDashboardMapper.xml +++ b/sonar-core/src/main/resources/org/sonar/core/dashboard/ActiveDashboardMapper.xml @@ -36,4 +36,21 @@ ORDER BY order_index ASC + + diff --git a/sonar-core/src/main/resources/org/sonar/core/measure/db/MeasureMapper.xml b/sonar-core/src/main/resources/org/sonar/core/measure/db/MeasureMapper.xml index 4a65b908e35..110a48de4e8 100644 --- a/sonar-core/src/main/resources/org/sonar/core/measure/db/MeasureMapper.xml +++ b/sonar-core/src/main/resources/org/sonar/core/measure/db/MeasureMapper.xml @@ -83,4 +83,11 @@ ) + + diff --git a/sonar-core/src/test/java/org/sonar/core/dashboard/ActiveDashboardDaoTest.java b/sonar-core/src/test/java/org/sonar/core/dashboard/ActiveDashboardDaoTest.java index bfcffe66513..4f3ec24bae7 100644 --- a/sonar-core/src/test/java/org/sonar/core/dashboard/ActiveDashboardDaoTest.java +++ b/sonar-core/src/test/java/org/sonar/core/dashboard/ActiveDashboardDaoTest.java @@ -98,4 +98,18 @@ public class ActiveDashboardDaoTest { assertThat(dao.selectGlobalDashboardsForUserLogin("obiwan")).hasSize(2).extracting("id").containsExactly(2L, 1L); } + + @Test + public void should_get_project_dashboards_for_anonymous() throws Exception { + dbTester.prepareDbUnit(getClass(), "shouldSelectProjectDashboardsForAnonymous.xml"); + + assertThat(dao.selectProjectDashboardsForUserLogin(null)).hasSize(2).extracting("id").containsExactly(2L, 1L); + } + + @Test + public void should_get_project_dashboards_for_user() throws Exception { + dbTester.prepareDbUnit(getClass(), "shouldSelectProjectDashboardsForUser.xml"); + + assertThat(dao.selectProjectDashboardsForUserLogin("obiwan")).hasSize(2).extracting("id").containsExactly(2L, 1L); + } } diff --git a/sonar-core/src/test/resources/org/sonar/core/dashboard/ActiveDashboardDaoTest/shouldSelectProjectDashboardsForAnonymous.xml b/sonar-core/src/test/resources/org/sonar/core/dashboard/ActiveDashboardDaoTest/shouldSelectProjectDashboardsForAnonymous.xml new file mode 100644 index 00000000000..850d6a51ace --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/dashboard/ActiveDashboardDaoTest/shouldSelectProjectDashboardsForAnonymous.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + diff --git a/sonar-core/src/test/resources/org/sonar/core/dashboard/ActiveDashboardDaoTest/shouldSelectProjectDashboardsForUser.xml b/sonar-core/src/test/resources/org/sonar/core/dashboard/ActiveDashboardDaoTest/shouldSelectProjectDashboardsForUser.xml new file mode 100644 index 00000000000..88de79b9ffc --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/dashboard/ActiveDashboardDaoTest/shouldSelectProjectDashboardsForUser.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceTypes.java b/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceTypes.java index 5fcf4e934d3..83962edd5f8 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceTypes.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceTypes.java @@ -57,6 +57,10 @@ public class ResourceTypes implements TaskComponent, ServerComponent { private final Map typeByQualifier; private final Collection rootTypes; + public ResourceTypes() { + this(new ResourceTypeTree[0]); + } + public ResourceTypes(ResourceTypeTree[] trees) { Preconditions.checkNotNull(trees); -- 2.39.5