diff options
author | Pierre <pierre.guillot@sonarsource.com> | 2022-07-07 15:29:09 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-07-12 14:30:04 +0000 |
commit | b7337e90fe3a896367ec2f4dda1684e0a2b177a8 (patch) | |
tree | af548493c8322882a5a20aef4a4b2f506cac7725 | |
parent | 72f42f71246601936ee5cea352f587b0e8da6640 (diff) | |
download | sonarqube-b7337e90fe3a896367ec2f4dda1684e0a2b177a8.tar.gz sonarqube-b7337e90fe3a896367ec2f4dda1684e0a2b177a8.zip |
SONAR-16599 Create a new endpoint to dismiss notice
9 files changed, 275 insertions, 7 deletions
diff --git a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/property/PropertyDbTester.java b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/property/PropertyDbTester.java index 33793c32f6f..76f9af0eee1 100644 --- a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/property/PropertyDbTester.java +++ b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/property/PropertyDbTester.java @@ -112,4 +112,12 @@ public class PropertyDbTester { return this; } + public Optional<PropertyDto> findFirstUserProperty(String userUuid, String key) { + PropertyQuery query = new PropertyQuery.Builder() + .setUserUuid(userUuid) + .setKey(key) + .build(); + + return dbClient.propertiesDao().selectByQuery(query, dbSession).stream().findFirst(); + } } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/CurrentAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/CurrentAction.java index fa3fba7c47d..4b956f4d894 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/CurrentAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/CurrentAction.java @@ -34,6 +34,7 @@ import org.sonar.db.component.BranchDto; import org.sonar.db.component.ComponentDto; import org.sonar.db.permission.GlobalPermission; import org.sonar.db.project.ProjectDto; +import org.sonar.db.property.PropertyQuery; import org.sonar.db.user.UserDto; import org.sonar.server.issue.AvatarResolver; import org.sonar.server.permission.PermissionService; @@ -49,6 +50,7 @@ import static java.util.Optional.ofNullable; import static java.util.stream.Collectors.toList; import static org.apache.commons.lang.StringUtils.EMPTY; import static org.sonar.api.web.UserRole.USER; +import static org.sonar.server.user.ws.DismissNoticeAction.GENERIC_CONCEPTS; import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonarqube.ws.Users.CurrentWsResponse.HomepageType.APPLICATION; import static org.sonarqube.ws.Users.CurrentWsResponse.HomepageType.PORTFOLIO; @@ -98,9 +100,9 @@ public class CurrentAction implements UsersWsAction { } } else { writeProtobuf(newBuilder() - .setIsLoggedIn(false) - .setPermissions(Permissions.newBuilder().addAllGlobal(getGlobalPermissions()).build()) - .build(), + .setIsLoggedIn(false) + .setPermissions(Permissions.newBuilder().addAllGlobal(getGlobalPermissions()).build()) + .build(), request, response); } } @@ -120,7 +122,8 @@ public class CurrentAction implements UsersWsAction { .setPermissions(Permissions.newBuilder().addAllGlobal(getGlobalPermissions()).build()) .setHomepage(buildHomepage(dbSession, user)) .setUsingSonarLintConnectedMode(user.getLastSonarlintConnectionDate() != null) - .setSonarLintAdSeen(user.isSonarlintAdSeen()); + .setSonarLintAdSeen(user.isSonarlintAdSeen()) + .putDismissedNotices(GENERIC_CONCEPTS, isNoticeDismissed(user, GENERIC_CONCEPTS)); ofNullable(emptyToNull(user.getEmail())).ifPresent(builder::setEmail); ofNullable(emptyToNull(user.getEmail())).ifPresent(u -> builder.setAvatar(avatarResolver.create(user))); ofNullable(user.getExternalLogin()).ifPresent(builder::setExternalIdentity); @@ -135,6 +138,18 @@ public class CurrentAction implements UsersWsAction { .collect(toList()); } + private boolean isNoticeDismissed(UserDto user, String noticeName) { + String paramKey = DismissNoticeAction.USER_DISMISS_CONSTANT + noticeName; + PropertyQuery query = new PropertyQuery.Builder() + .setUserUuid(user.getUuid()) + .setKey(paramKey) + .build(); + + try (DbSession dbSession = dbClient.openSession(false)) { + return !dbClient.propertiesDao().selectByQuery(query, dbSession).isEmpty(); + } + } + private CurrentWsResponse.Homepage buildHomepage(DbSession dbSession, UserDto user) { if (noHomepageSet(user)) { return defaultHomepage(); diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/DismissNoticeAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/DismissNoticeAction.java new file mode 100644 index 00000000000..57b14f59024 --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/DismissNoticeAction.java @@ -0,0 +1,87 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info 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.server.user.ws; + +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.property.PropertyDto; +import org.sonar.db.property.PropertyQuery; +import org.sonar.server.user.UserSession; + +import static com.google.common.base.Preconditions.checkState; + +public class DismissNoticeAction implements UsersWsAction { + + public static final String GENERIC_CONCEPTS = "genericConcepts"; + public static final String USER_DISMISS_CONSTANT = "user.dismissedNotices."; + + private final UserSession userSession; + private final DbClient dbClient; + + public DismissNoticeAction(UserSession userSession, DbClient dbClient) { + this.userSession = userSession; + this.dbClient = dbClient; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("dismiss_notice") + .setDescription("Dismiss a notice for the current user.") + .setSince("9.6") + .setInternal(true) + .setHandler(this) + .setPost(true); + + action.createParam("notice") + .setDescription("notice key to dismiss") + .setExampleValue(GENERIC_CONCEPTS) + .setPossibleValues(GENERIC_CONCEPTS); + } + + @Override + public void handle(Request request, Response response) throws Exception { + userSession.checkLoggedIn(); + String currentUserUuid = userSession.getUuid(); + checkState(currentUserUuid != null, "User uuid should not be null"); + + String noticeKeyParam = request.mandatoryParam("notice"); + + try (DbSession dbSession = dbClient.openSession(false)) { + String paramKey = USER_DISMISS_CONSTANT + noticeKeyParam; + PropertyQuery query = new PropertyQuery.Builder() + .setUserUuid(currentUserUuid) + .setKey(paramKey) + .build(); + + if (!dbClient.propertiesDao().selectByQuery(query, dbSession).isEmpty()) { + throw new IllegalArgumentException(String.format("Notice %s is already dismissed", noticeKeyParam)); + } + + PropertyDto property = new PropertyDto().setUserUuid(currentUserUuid).setKey(paramKey); + dbClient.propertiesDao().saveProperty(dbSession, property); + dbSession.commit(); + response.noContent(); + } + + } +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/UsersWsModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/UsersWsModule.java index b16d68eddd0..9247212af2e 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/UsersWsModule.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/UsersWsModule.java @@ -40,7 +40,8 @@ public class UsersWsModule extends Module { UserJsonWriter.class, SetHomepageAction.class, HomepageTypesImpl.class, - UpdateIdentityProviderAction.class); + UpdateIdentityProviderAction.class, + DismissNoticeAction.class); } } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java index e87d715a881..7e0c5f134cf 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java @@ -19,6 +19,8 @@ */ package org.sonar.server.user.ws; +import java.util.Map; +import org.assertj.core.groups.Tuple; import org.junit.Rule; import org.junit.Test; import org.sonar.api.resources.Qualifiers; @@ -30,6 +32,7 @@ import org.sonar.api.utils.System2; import org.sonar.core.platform.PlatformEditionProvider; import org.sonar.db.DbTester; import org.sonar.db.component.ComponentDto; +import org.sonar.db.property.PropertyDto; import org.sonar.db.user.UserDto; import org.sonar.server.issue.AvatarResolverImpl; import org.sonar.server.permission.PermissionService; @@ -85,6 +88,33 @@ public class CurrentActionTest { } @Test + public void return_generic_concepts_seen() { + UserDto user = db.users().insertUser(); + userSession.logIn(user); + + PropertyDto property = new PropertyDto().setUserUuid(user.getUuid()).setKey("user.dismissedNotices.genericConcepts"); + db.properties().insertProperties(userSession.getLogin(), null, null, null, property); + + CurrentWsResponse response = call(); + + assertThat(response.getDismissedNoticesMap().entrySet()) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .contains(Tuple.tuple("genericConcepts", true)); + } + + @Test + public void return_generic_concepts_not_seen() { + UserDto user = db.users().insertUser(); + userSession.logIn(user); + + CurrentWsResponse response = call(); + + assertThat(response.getDismissedNoticesMap().entrySet()) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .contains(Tuple.tuple("genericConcepts", false)); + } + + @Test public void return_minimal_user_info() { UserDto user = db.users().insertUser(u -> u .setLogin("obiwan.kenobi") diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/user/ws/DismissNoticeActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/user/ws/DismissNoticeActionTest.java new file mode 100644 index 00000000000..06457e7fc32 --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/user/ws/DismissNoticeActionTest.java @@ -0,0 +1,110 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info 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.server.user.ws; + +import java.util.Optional; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.db.DbTester; +import org.sonar.db.property.PropertyDto; +import org.sonar.server.exceptions.UnauthorizedException; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.TestRequest; +import org.sonar.server.ws.TestResponse; +import org.sonar.server.ws.WsActionTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class DismissNoticeActionTest { + + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + @Rule + public UserSessionRule userSessionRule = UserSessionRule.standalone(); + + private final WsActionTester tester = new WsActionTester(new DismissNoticeAction(userSessionRule, db.getDbClient())); + + @Test + public void dismiss_genericConcepts() { + userSessionRule.logIn(); + + TestResponse testResponse = tester.newRequest() + .setParam("notice", "genericConcepts") + .execute(); + + assertThat(testResponse.getStatus()).isEqualTo(204); + + Optional<PropertyDto> propertyDto = db.properties().findFirstUserProperty(userSessionRule.getUuid(), "user.dismissedNotices.genericConcepts"); + assertThat(propertyDto).isPresent(); + } + + + @Test + public void authentication_is_required() { + TestRequest testRequest = tester.newRequest() + .setParam("notice", "anyValue"); + + assertThatThrownBy(testRequest::execute) + .isInstanceOf(UnauthorizedException.class) + .hasMessage("Authentication is required"); + } + + + @Test + public void notice_parameter_is_mandatory() { + userSessionRule.logIn(); + TestRequest testRequest = tester.newRequest(); + + assertThatThrownBy(testRequest::execute) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("The 'notice' parameter is missing"); + } + + + @Test + public void notice_not_supported() { + userSessionRule.logIn(); + TestRequest testRequest = tester.newRequest() + .setParam("notice", "not_supported_value"); + + assertThatThrownBy(testRequest::execute) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Value of parameter 'notice' (not_supported_value) must be one of: [genericConcepts]"); + } + + + @Test + public void notice_already_exist() { + userSessionRule.logIn(); + PropertyDto property = new PropertyDto().setKey("user.dismissedNotices.genericConcepts").setUserUuid(userSessionRule.getUuid()); + db.properties().insertProperties(userSessionRule.getLogin(), null, null, null, property); + + TestRequest testRequest = tester.newRequest() + .setParam("notice", "genericConcepts"); + + assertThatThrownBy(testRequest::execute) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Notice genericConcepts is already dismissed"); + } + + +}
\ No newline at end of file diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/user/ws/UsersWsModuleTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/user/ws/UsersWsModuleTest.java index a86017c16ad..d3d195cb172 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/user/ws/UsersWsModuleTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/user/ws/UsersWsModuleTest.java @@ -27,9 +27,9 @@ import static org.assertj.core.api.Assertions.assertThat; public class UsersWsModuleTest { @Test - public void verify_count_of_added_components() { + public void verify_container_is_not_empty() { ListContainer container = new ListContainer(); new UsersWsModule().configure(container); - assertThat(container.getAddedObjects()).hasSize(15); + assertThat(container.getAddedObjects()).isNotEmpty(); } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/users/UsersService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/users/UsersService.java index f0b83571dd4..0bdf172ec5c 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/users/UsersService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/users/UsersService.java @@ -230,4 +230,20 @@ public class UsersService extends BaseService { .setMediaType(MediaTypes.JSON) ).content(); } + + + /** + * + * This is part of the internal API. + * This is a POST request. + * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/users/dismiss_notice">Further information about this action online (including a response example)</a> + * @since 9.6 + */ + public void dismissNotice(String notice) { + call( + new PostRequest(path("dismiss_notice")) + .setParam("notice", notice) + ).content(); + } + } diff --git a/sonar-ws/src/main/protobuf/ws-users.proto b/sonar-ws/src/main/protobuf/ws-users.proto index 52579c59150..462aa020fc8 100644 --- a/sonar-ws/src/main/protobuf/ws-users.proto +++ b/sonar-ws/src/main/protobuf/ws-users.proto @@ -113,6 +113,7 @@ message CurrentWsResponse { reserved 15; // settings removed optional bool usingSonarLintConnectedMode = 16; optional bool sonarLintAdSeen = 17; + map<string,bool> dismissedNotices = 18; message Permissions { repeated string global = 1; |