From 0ecd8610552ccd59049308c1be46b03470c48538 Mon Sep 17 00:00:00 2001 From: Michal Duda Date: Fri, 18 Sep 2020 17:08:48 +0200 Subject: [PATCH] SONAR-13862 Add a WS for dismissing analysis warning by the current user --- .../HandleUnanalyzedLanguagesStep.java | 20 +- .../HandleUnanalyzedLanguagesStepTest.java | 38 ++-- .../step/PersistAnalysisWarningsStepTest.java | 13 +- .../org/sonar/ce/task/log/CeTaskMessages.java | 15 +- .../sonar/ce/task/log/CeTaskMessagesImpl.java | 10 +- .../java/org/sonar/db/version/SqTables.java | 1 + .../src/main/java/org/sonar/db/DaoModule.java | 2 + .../src/main/java/org/sonar/db/DbClient.java | 7 + .../src/main/java/org/sonar/db/MyBatis.java | 2 + .../org/sonar/db/ce/CeTaskMessageDao.java | 9 + .../org/sonar/db/ce/CeTaskMessageDto.java | 24 +- .../org/sonar/db/ce/CeTaskMessageMapper.java | 4 + .../org/sonar/db/ce/CeTaskMessageType.java | 35 +++ .../org/sonar/db/purge/PurgeCommands.java | 7 + .../java/org/sonar/db/purge/PurgeDao.java | 1 + .../java/org/sonar/db/purge/PurgeMapper.java | 2 + .../db/user/UserDismissedMessageDto.java | 92 ++++++++ .../db/user/UserDismissedMessagesDao.java | 63 ++++++ .../db/user/UserDismissedMessagesMapper.java | 37 ++++ .../org/sonar/db/ce/CeTaskMessageMapper.xml | 25 ++- .../org/sonar/db/purge/PurgeMapper.xml | 4 + .../db/user/UserDismissedMessagesMapper.xml | 60 +++++ server/sonar-db-dao/src/schema/schema-sq.ddl | 15 +- .../org/sonar/db/ce/CeActivityDaoTest.java | 1 + .../org/sonar/db/ce/CeTaskMessageDaoTest.java | 54 ++++- .../org/sonar/db/purge/PurgeCommandsTest.java | 17 ++ .../java/org/sonar/db/purge/PurgeDaoTest.java | 32 ++- .../db/user/UserDismissedMessagesDaoTest.java | 152 +++++++++++++ .../java/org/sonar/db/user/UserDbTester.java | 13 ++ ...MessageTypeColumnOfCeTaskMessageTable.java | 42 ++++ ...essageTypeColumnToCeTaskMessageTable.java} | 16 +- .../v85/CreateUserDismissedMessagesTable.java | 102 +++++++++ .../db/migration/version/v85/DbVersion85.java | 10 +- ...olumnNotNullableOnCeTaskMessageTable.java} | 21 +- ...essageTypeColumnOfCeTaskMessageTable.java} | 14 +- ...geTypeColumnOfCeTaskMessageTableTest.java} | 16 +- ...ageTypeColumnToCeTaskMessageTableTest.java | 63 ++++++ .../CreateUserDismissedMessagesTableTest.java | 55 +++++ ...nNotNullableOnCeTaskMessageTableTest.java} | 13 +- ...issibleColumnOfCeTaskMessageTableTest.java | 87 -------- ...ageTypeColumnOfCeTaskMessageTableTest.java | 73 ++++++ .../schema.sql | 9 + .../schema.sql | 9 + .../schema.sql | 9 + .../schema.sql | 9 + .../startup/UpgradeSuggestionsCleaner.java | 70 ++++++ .../UpgradeSuggestionsCleanerTest.java | 148 +++++++++++++ .../server/ce/ws/AnalysisStatusAction.java | 2 +- .../org/sonar/server/ce/ws/CeWsModule.java | 3 +- .../ce/ws/DismissAnalysisWarningAction.java | 115 ++++++++++ .../server/user/ws/DeactivateAction.java | 5 +- .../server/ce/ws/ActivityActionTest.java | 2 + .../ce/ws/AnalysisStatusActionTest.java | 11 +- .../sonar/server/ce/ws/CeWsModuleTest.java | 2 +- .../server/ce/ws/ComponentActionTest.java | 2 + .../ws/DismissAnalysisWarningActionTest.java | 208 ++++++++++++++++++ .../sonar/server/ce/ws/TaskActionTest.java | 11 +- .../server/user/ws/DeactivateActionTest.java | 40 +++- .../platformlevel/PlatformLevelStartup.java | 4 +- .../org/sonarqube/ws/client/ce/CeService.java | 16 ++ .../ce/DismissAnalysisWarningRequest.java | 59 +++++ 61 files changed, 1775 insertions(+), 226 deletions(-) create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageType.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDismissedMessageDto.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDismissedMessagesDao.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDismissedMessagesMapper.java create mode 100644 server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserDismissedMessagesMapper.xml create mode 100644 server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDismissedMessagesDaoTest.java create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/AddIndexOnMessageTypeColumnOfCeTaskMessageTable.java rename server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/{AddIsDismissibleColumnToCeTaskMessageTable.java => AddMessageTypeColumnToCeTaskMessageTable.java} (72%) create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/CreateUserDismissedMessagesTable.java rename server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/{MakeIsDismissibleColumnNotNullableOnCeTaskMessageTable.java => MakeMessageTypeColumnNotNullableOnCeTaskMessageTable.java} (65%) rename server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/{PopulateIsDismissibleColumnOfCeTaskMessageTable.java => PopulateMessageTypeColumnOfCeTaskMessageTable.java} (78%) rename server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/{AddIsDismissibleColumnToCeTaskMessageTableTest.java => AddIndexOnMessageTypeColumnOfCeTaskMessageTableTest.java} (68%) create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/AddMessageTypeColumnToCeTaskMessageTableTest.java create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/CreateUserDismissedMessagesTableTest.java rename server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/{MakeIsDismissibleColumnNotNullableOnCeTaskMessageTableTest.java => MakeMessageTypeColumnNotNullableOnCeTaskMessageTableTest.java} (68%) delete mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/PopulateIsDismissibleColumnOfCeTaskMessageTableTest.java create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/PopulateMessageTypeColumnOfCeTaskMessageTableTest.java create mode 100644 server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/AddIndexOnMessageTypeColumnOfCeTaskMessageTableTest/schema.sql create mode 100644 server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/AddMessageTypeColumnToCeTaskMessageTableTest/schema.sql create mode 100644 server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/MakeMessageTypeColumnNotNullableOnCeTaskMessageTableTest/schema.sql create mode 100644 server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/PopulateMessageTypeColumnOfCeTaskMessageTableTest/schema.sql create mode 100644 server/sonar-webserver-core/src/main/java/org/sonar/server/startup/UpgradeSuggestionsCleaner.java create mode 100644 server/sonar-webserver-core/src/test/java/org/sonar/server/startup/UpgradeSuggestionsCleanerTest.java create mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/ce/ws/DismissAnalysisWarningAction.java create mode 100644 server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/DismissAnalysisWarningActionTest.java create mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/ce/DismissAnalysisWarningRequest.java diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/language/HandleUnanalyzedLanguagesStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/language/HandleUnanalyzedLanguagesStep.java index d5c78acab54..bd29a24839f 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/language/HandleUnanalyzedLanguagesStep.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/language/HandleUnanalyzedLanguagesStep.java @@ -36,6 +36,7 @@ import org.sonar.ce.task.projectanalysis.metric.MetricRepository; import org.sonar.ce.task.step.ComputationStep; import org.sonar.core.platform.EditionProvider; import org.sonar.core.platform.PlatformEditionProvider; +import org.sonar.db.ce.CeTaskMessageType; import static java.lang.String.format; import static org.sonar.core.language.UnanalyzedLanguages.C; @@ -50,9 +51,9 @@ public class HandleUnanalyzedLanguagesStep implements ComputationStep { static final String DESCRIPTION = "Check upgrade possibility for not analyzed code files."; - private static final String LANGUAGE_UPGRADE_MESSAGE = "%s file(s) detected during the last analysis. %s code cannot be analyzed with SonarQube " + - "community edition. Please consider upgrading to " + - "the Developer Edition to analyze this language."; + private static final String LANGUAGE_UPGRADE_MESSAGE = "%s detected in this project during the last analysis. %s cannot be analyzed with your" + + " current SonarQube edition. Please consider upgrading to" + + " Developer Edition to find Bugs, Code Smells, Vulnerabilities and Security Hotspots in %s."; private final BatchReportReader reportReader; private final CeTaskMessages ceTaskMessages; @@ -102,7 +103,7 @@ public class HandleUnanalyzedLanguagesStep implements ComputationStep { Iterator> iterator = sortedLanguageMap.entrySet().iterator(); Map.Entry firstLanguage = iterator.next(); StringBuilder languageLabel = new StringBuilder(firstLanguage.getKey()); - StringBuilder fileCountLabel = new StringBuilder(format("%s %s", firstLanguage.getValue(), firstLanguage.getKey())); + StringBuilder fileCountLabel = new StringBuilder(format("%s unanalyzed %s", firstLanguage.getValue(), firstLanguage.getKey())); while (iterator.hasNext()) { Map.Entry nextLanguage = iterator.next(); if (iterator.hasNext()) { @@ -113,10 +114,17 @@ public class HandleUnanalyzedLanguagesStep implements ComputationStep { fileCountLabel.append(" and "); } languageLabel.append(nextLanguage.getKey()); - fileCountLabel.append(format("%s %s", nextLanguage.getValue(), nextLanguage.getKey())); + fileCountLabel.append(format("%s unanalyzed %s", nextLanguage.getValue(), nextLanguage.getKey())); } - return new CeTaskMessages.Message(format(LANGUAGE_UPGRADE_MESSAGE, fileCountLabel, languageLabel), system.now(), true); + if (sortedLanguageMap.size() == 1 && sortedLanguageMap.entrySet().iterator().next().getValue() == 1) { + fileCountLabel.append(" file was"); + } else { + fileCountLabel.append(" files were"); + } + + String message = format(LANGUAGE_UPGRADE_MESSAGE, fileCountLabel, languageLabel, sortedLanguageMap.size() == 1 ? "this file" : "these files"); + return new CeTaskMessages.Message(message, system.now(), CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); } private void computeMeasures(Map filesPerLanguage) { diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/language/HandleUnanalyzedLanguagesStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/language/HandleUnanalyzedLanguagesStepTest.java index 6aba65c5494..bc0726aac4b 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/language/HandleUnanalyzedLanguagesStepTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/language/HandleUnanalyzedLanguagesStepTest.java @@ -24,7 +24,10 @@ import java.util.List; import java.util.Optional; import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.junit.MockitoJUnitRunner; import org.sonar.api.utils.System2; import org.sonar.ce.task.log.CeTaskMessages; import org.sonar.ce.task.log.CeTaskMessages.Message; @@ -37,6 +40,7 @@ import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule; import org.sonar.ce.task.step.TestComputationStepContext; import org.sonar.core.platform.EditionProvider; import org.sonar.core.platform.PlatformEditionProvider; +import org.sonar.db.ce.CeTaskMessageType; import org.sonar.scanner.protocol.output.ScannerReport; import static com.google.common.collect.ImmutableList.of; @@ -54,6 +58,7 @@ import static org.sonar.server.metric.UnanalyzedLanguageMetrics.UNANALYZED_CPP; import static org.sonar.server.metric.UnanalyzedLanguageMetrics.UNANALYZED_CPP_KEY; import static org.sonar.server.metric.UnanalyzedLanguageMetrics.UNANALYZED_C_KEY; +@RunWith(MockitoJUnitRunner.class) public class HandleUnanalyzedLanguagesStepTest { private static final int PROJECT_REF = 1; @@ -70,6 +75,9 @@ public class HandleUnanalyzedLanguagesStepTest { @Rule public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository); + @Captor + private ArgumentCaptor argumentCaptor; + private final PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class); private final CeTaskMessages ceTaskMessages = mock(CeTaskMessages.class); @@ -93,18 +101,18 @@ public class HandleUnanalyzedLanguagesStepTest { .putNotAnalyzedFilesByLanguage("C", 10) .putNotAnalyzedFilesByLanguage("SomeLang", 1000) .build()); - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Message.class); underTest.execute(new TestComputationStepContext()); verify(ceTaskMessages, times(1)).add(argumentCaptor.capture()); assertThat(argumentCaptor.getAllValues()) - .extracting(Message::getText, Message::isDismissible) + .extracting(Message::getText, Message::getType) .containsExactly(tuple( - "10 C, 20 C++ and 1000 SomeLang file(s) detected during the last analysis. C, C++ and SomeLang code cannot be analyzed with SonarQube community " + - "edition. Please consider upgrading to the Developer " + - "Edition to analyze this language.", - true)); + "10 unanalyzed C, 20 unanalyzed C++ and 1000 unanalyzed SomeLang files were detected in this project during the last analysis. C," + + " C++ and SomeLang cannot be analyzed with your current SonarQube edition. Please consider" + + " upgrading to Developer Edition to find Bugs," + + " Code Smells, Vulnerabilities and Security Hotspots in these files.", + CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE)); assertThat(measureRepository.getAddedRawMeasure(PROJECT_REF, UNANALYZED_C_KEY).get().getIntValue()).isEqualTo(10); assertThat(measureRepository.getAddedRawMeasure(PROJECT_REF, UNANALYZED_CPP_KEY).get().getIntValue()).isEqualTo(20); } @@ -115,16 +123,15 @@ public class HandleUnanalyzedLanguagesStepTest { reportReader.setMetadata(ScannerReport.Metadata.newBuilder() .putNotAnalyzedFilesByLanguage("C", 10) .build()); - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(CeTaskMessages.Message.class); underTest.execute(new TestComputationStepContext()); verify(ceTaskMessages, times(1)).add(argumentCaptor.capture()); List messages = argumentCaptor.getAllValues(); assertThat(messages).extracting(CeTaskMessages.Message::getText).containsExactly( - "10 C file(s) detected during the last analysis. C code cannot be analyzed with SonarQube community " + - "edition. Please consider upgrading to the Developer " + - "Edition to analyze this language."); + "10 unanalyzed C files were detected in this project during the last analysis. C cannot be analyzed with your current SonarQube edition. Please" + + " consider upgrading to Developer" + + " Edition to find Bugs, Code Smells, Vulnerabilities and Security Hotspots in this file."); assertThat(measureRepository.getAddedRawMeasure(PROJECT_REF, UNANALYZED_C_KEY).get().getIntValue()).isEqualTo(10); assertThat(measureRepository.getAddedRawMeasure(PROJECT_REF, UNANALYZED_CPP_KEY)).isEmpty(); } @@ -133,19 +140,18 @@ public class HandleUnanalyzedLanguagesStepTest { public void adds_warning_in_SQ_community_edition_if_there_are_cpp_files() { when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.COMMUNITY)); reportReader.setMetadata(ScannerReport.Metadata.newBuilder() - .putNotAnalyzedFilesByLanguage("C++", 9) + .putNotAnalyzedFilesByLanguage("C++", 1) .build()); - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(CeTaskMessages.Message.class); underTest.execute(new TestComputationStepContext()); verify(ceTaskMessages, times(1)).add(argumentCaptor.capture()); List messages = argumentCaptor.getAllValues(); assertThat(messages).extracting(CeTaskMessages.Message::getText).containsExactly( - "9 C++ file(s) detected during the last analysis. C++ code cannot be analyzed with SonarQube community " + - "edition. Please consider upgrading to the Developer " + - "Edition to analyze this language."); - assertThat(measureRepository.getAddedRawMeasure(PROJECT_REF, UNANALYZED_CPP_KEY).get().getIntValue()).isEqualTo(9); + "1 unanalyzed C++ file was detected in this project during the last analysis. C++ cannot be analyzed with your current SonarQube edition. Please" + + " consider upgrading to Developer" + + " Edition to find Bugs, Code Smells, Vulnerabilities and Security Hotspots in this file."); + assertThat(measureRepository.getAddedRawMeasure(PROJECT_REF, UNANALYZED_CPP_KEY).get().getIntValue()).isEqualTo(1); assertThat(measureRepository.getAddedRawMeasure(PROJECT_REF, UNANALYZED_C_KEY)).isEmpty(); } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisWarningsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisWarningsStepTest.java index 0024c611644..8f5224a0640 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisWarningsStepTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisWarningsStepTest.java @@ -23,10 +23,14 @@ import com.google.common.collect.ImmutableList; import java.util.List; import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.junit.MockitoJUnitRunner; import org.sonar.ce.task.log.CeTaskMessages; import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule; import org.sonar.ce.task.step.TestComputationStepContext; +import org.sonar.db.ce.CeTaskMessageType; import org.sonar.scanner.protocol.output.ScannerReport; import static com.google.common.collect.ImmutableList.of; @@ -38,11 +42,15 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; +@RunWith(MockitoJUnitRunner.class) public class PersistAnalysisWarningsStepTest { @Rule public BatchReportReaderRule reportReader = new BatchReportReaderRule(); + @Captor + private ArgumentCaptor> argumentCaptor; + private final CeTaskMessages ceTaskMessages = mock(CeTaskMessages.class); private final PersistAnalysisWarningsStep underTest = new PersistAnalysisWarningsStep(reportReader, ceTaskMessages); @@ -57,14 +65,13 @@ public class PersistAnalysisWarningsStepTest { ScannerReport.AnalysisWarning warning2 = ScannerReport.AnalysisWarning.newBuilder().setText("warning 2").build(); ImmutableList warnings = of(warning1, warning2); reportReader.setAnalysisWarnings(warnings); - ArgumentCaptor> argumentCaptor = ArgumentCaptor.forClass(List.class); underTest.execute(new TestComputationStepContext()); verify(ceTaskMessages, times(1)).addAll(argumentCaptor.capture()); assertThat(argumentCaptor.getValue()) - .extracting(CeTaskMessages.Message::getText, CeTaskMessages.Message::isDismissible) - .containsExactly(tuple("warning 1", false), tuple("warning 2", false)); + .extracting(CeTaskMessages.Message::getText, CeTaskMessages.Message::getType) + .containsExactly(tuple("warning 1", CeTaskMessageType.GENERIC), tuple("warning 2", CeTaskMessageType.GENERIC)); } @Test diff --git a/server/sonar-ce-task/src/main/java/org/sonar/ce/task/log/CeTaskMessages.java b/server/sonar-ce-task/src/main/java/org/sonar/ce/task/log/CeTaskMessages.java index 7e92a255c10..53d043c934f 100644 --- a/server/sonar-ce-task/src/main/java/org/sonar/ce/task/log/CeTaskMessages.java +++ b/server/sonar-ce-task/src/main/java/org/sonar/ce/task/log/CeTaskMessages.java @@ -23,6 +23,7 @@ import java.util.Collection; import java.util.Objects; import javax.annotation.concurrent.Immutable; import org.sonar.api.ce.ComputeEngineSide; +import org.sonar.db.ce.CeTaskMessageType; import static com.google.common.base.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; @@ -48,19 +49,19 @@ public interface CeTaskMessages { class Message { private final String text; private final long timestamp; - private final boolean dismissible; + private final CeTaskMessageType type; - public Message(String text, long timestamp, boolean dismissible) { + public Message(String text, long timestamp, CeTaskMessageType type) { requireNonNull(text, "Text can't be null"); checkArgument(!text.isEmpty(), "Text can't be empty"); checkArgument(timestamp >= 0, "Timestamp can't be less than 0"); this.text = text; this.timestamp = timestamp; - this.dismissible = dismissible; + this.type = type; } public Message(String text, long timestamp) { - this(text, timestamp, false); + this(text, timestamp, CeTaskMessageType.GENERIC); } public String getText() { @@ -71,8 +72,8 @@ public interface CeTaskMessages { return timestamp; } - public boolean isDismissible() { - return dismissible; + public CeTaskMessageType getType() { + return type; } @Override @@ -98,7 +99,7 @@ public interface CeTaskMessages { return "Message{" + "text='" + text + '\'' + ", timestamp=" + timestamp + - ", dismissible=" + dismissible + + ", type=" + type + '}'; } } diff --git a/server/sonar-ce-task/src/main/java/org/sonar/ce/task/log/CeTaskMessagesImpl.java b/server/sonar-ce-task/src/main/java/org/sonar/ce/task/log/CeTaskMessagesImpl.java index 153e9ce393d..f4072138cf8 100644 --- a/server/sonar-ce-task/src/main/java/org/sonar/ce/task/log/CeTaskMessagesImpl.java +++ b/server/sonar-ce-task/src/main/java/org/sonar/ce/task/log/CeTaskMessagesImpl.java @@ -71,11 +71,11 @@ public class CeTaskMessagesImpl implements CeTaskMessages { public void insert(DbSession dbSession, Message message) { dbClient.ceTaskMessageDao().insert(dbSession, new CeTaskMessageDto() - .setUuid(uuidFactory.create()) - .setTaskUuid(ceTask.getUuid()) - .setMessage(message.getText()) - .setDismissible(message.isDismissible()) - .setCreatedAt(message.getTimestamp())); + .setUuid(uuidFactory.create()) + .setTaskUuid(ceTask.getUuid()) + .setMessage(message.getText()) + .setType(message.getType()) + .setCreatedAt(message.getTimestamp())); } private static void checkMessage(Message message) { diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java index 6c6f9df6e4f..4c2f24626c0 100644 --- a/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java +++ b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java @@ -115,6 +115,7 @@ public final class SqTables { "session_tokens", "snapshots", "users", + "user_dismissed_messages", "user_properties", "user_roles", "user_tokens", diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java index 9a89ca6b267..205d02d18e2 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java @@ -86,6 +86,7 @@ import org.sonar.db.user.RoleDao; import org.sonar.db.user.SamlMessageIdDao; import org.sonar.db.user.SessionTokensDao; import org.sonar.db.user.UserDao; +import org.sonar.db.user.UserDismissedMessagesDao; import org.sonar.db.user.UserGroupDao; import org.sonar.db.user.UserPropertiesDao; import org.sonar.db.user.UserTokenDao; @@ -159,6 +160,7 @@ public class DaoModule extends Module { SchemaMigrationDao.class, SessionTokensDao.class, UserDao.class, + UserDismissedMessagesDao.class, UserGroupDao.class, UserPermissionDao.class, UserPropertiesDao.class, diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java index 022e0c4dc39..78d717373ed 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java @@ -84,6 +84,7 @@ import org.sonar.db.user.RoleDao; import org.sonar.db.user.SamlMessageIdDao; import org.sonar.db.user.SessionTokensDao; import org.sonar.db.user.UserDao; +import org.sonar.db.user.UserDismissedMessagesDao; import org.sonar.db.user.UserGroupDao; import org.sonar.db.user.UserPropertiesDao; import org.sonar.db.user.UserTokenDao; @@ -164,6 +165,7 @@ public class DbClient { private final ProjectDao projectDao; private final SessionTokensDao sessionTokensDao; private final SamlMessageIdDao samlMessageIdDao; + private final UserDismissedMessagesDao userDismissedMessagesDao; public DbClient(Database database, MyBatis myBatis, DBSessions dbSessions, Dao... daos) { this.database = database; @@ -242,6 +244,7 @@ public class DbClient { projectDao = getDao(map, ProjectDao.class); sessionTokensDao = getDao(map, SessionTokensDao.class); samlMessageIdDao = getDao(map, SamlMessageIdDao.class); + userDismissedMessagesDao = getDao(map, UserDismissedMessagesDao.class); } public DbSession openSession(boolean batch) { @@ -534,4 +537,8 @@ public class DbClient { return samlMessageIdDao; } + public UserDismissedMessagesDao userDismissedMessagesDao() { + return userDismissedMessagesDao; + } + } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java index bc390ff1fb1..8c1f4a3b487 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java @@ -140,6 +140,7 @@ import org.sonar.db.user.GroupMembershipMapper; import org.sonar.db.user.RoleMapper; import org.sonar.db.user.SamlMessageIdMapper; import org.sonar.db.user.SessionTokenMapper; +import org.sonar.db.user.UserDismissedMessagesMapper; import org.sonar.db.user.UserDto; import org.sonar.db.user.UserGroupDto; import org.sonar.db.user.UserGroupMapper; @@ -287,6 +288,7 @@ public class MyBatis implements Startable { SchemaMigrationMapper.class, SessionTokenMapper.class, SnapshotMapper.class, + UserDismissedMessagesMapper.class, UserGroupMapper.class, UserMapper.class, UserPermissionMapper.class, diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageDao.java index 6604c0bb682..39c4b8e2fad 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageDao.java @@ -20,6 +20,7 @@ package org.sonar.db.ce; import java.util.List; +import java.util.Optional; import org.sonar.db.Dao; import org.sonar.db.DbSession; @@ -28,6 +29,10 @@ public class CeTaskMessageDao implements Dao { getMapper(dbSession).insert(dto); } + public Optional selectByUuid(DbSession dbSession, String uuid) { + return getMapper(dbSession).selectByUuid(uuid); + } + /** * @return the messages for the specific task, if any, in ascending order of column {@code CREATED_AT}. */ @@ -35,6 +40,10 @@ public class CeTaskMessageDao implements Dao { return getMapper(dbSession).selectByTask(taskUuid); } + public void deleteByType(DbSession session, CeTaskMessageType type) { + getMapper(session).deleteByType(type.name()); + } + private static CeTaskMessageMapper getMapper(DbSession dbSession) { return dbSession.getMapper(CeTaskMessageMapper.class); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageDto.java index f31bcbc2530..3eb99a6dce4 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageDto.java @@ -35,13 +35,13 @@ public class CeTaskMessageDto { */ private String message; /** - * Timestamp the message was created. Not null + * Type of the message */ - private long createdAt; + private CeTaskMessageType type; /** - * Information if this message can be dismissed by the user + * Timestamp the message was created. Not null */ - private boolean dismissible; + private long createdAt; public String getUuid() { return uuid; @@ -72,21 +72,21 @@ public class CeTaskMessageDto { return this; } - public long getCreatedAt() { - return createdAt; + public CeTaskMessageType getType() { + return type; } - public CeTaskMessageDto setCreatedAt(long createdAt) { - this.createdAt = createdAt; + public CeTaskMessageDto setType(CeTaskMessageType type) { + this.type = type; return this; } - public boolean isDismissible() { - return dismissible; + public long getCreatedAt() { + return createdAt; } - public CeTaskMessageDto setDismissible(boolean dismissible) { - this.dismissible = dismissible; + public CeTaskMessageDto setCreatedAt(long createdAt) { + this.createdAt = createdAt; return this; } } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageMapper.java index 5ec953f3b1d..619cd743310 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageMapper.java @@ -20,11 +20,15 @@ package org.sonar.db.ce; import java.util.List; +import java.util.Optional; import org.apache.ibatis.annotations.Param; public interface CeTaskMessageMapper { void insert(@Param("dto") CeTaskMessageDto dto); + Optional selectByUuid(@Param("uuid") String uuid); + List selectByTask(@Param("taskUuid") String taskUuid); + void deleteByType(@Param("ceMessageType") String ceMessageType); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageType.java b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageType.java new file mode 100644 index 00000000000..52bd4b38038 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageType.java @@ -0,0 +1,35 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.db.ce; + +public enum CeTaskMessageType { + GENERIC(false), + SUGGEST_DEVELOPER_EDITION_UPGRADE(true); + + private final boolean dismissible; + + CeTaskMessageType(boolean dismissible) { + this.dismissible = dismissible; + } + + public boolean isDismissible() { + return dismissible; + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java index 33abd185e2e..b11c7febd7f 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java @@ -434,4 +434,11 @@ class PurgeCommands { profiler.stop(); } + void deleteUserDismissedMessages(String projectUuid) { + profiler.start("deleteUserDismissedMessages (user_dismissed_messages)"); + purgeMapper.deleteUserDismissedMessagesByProjectUuid(projectUuid); + session.commit(); + profiler.stop(); + } + } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java index e9a0af96f6d..bb39d91a389 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java @@ -224,6 +224,7 @@ public class PurgeDao implements Dao { commands.deleteComponents(rootUuid); commands.deleteComponentsByMainBranchProjectUuid(rootUuid); commands.deleteProject(rootUuid); + commands.deleteUserDismissedMessages(rootUuid); } /** diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java index 56f09dda6b6..840533516f9 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java @@ -150,4 +150,6 @@ public interface PurgeMapper { void deleteNewCodePeriodsByRootUuid(String rootUuid); void deleteProjectAlmSettingsByProjectUuid(@Param("projectUuid") String projectUuid); + + void deleteUserDismissedMessagesByProjectUuid(@Param("projectUuid") String projectUuid); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDismissedMessageDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDismissedMessageDto.java new file mode 100644 index 00000000000..96d0d3e8219 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDismissedMessageDto.java @@ -0,0 +1,92 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.db.user; + +import org.sonar.db.ce.CeTaskMessageType; + +public class UserDismissedMessageDto { + + private String uuid; + /** + * Uuid of the user that dismissed the message type + */ + private String userUuid; + /** + * Uuid of the project for which the message type was dismissed + */ + private String projectUuid; + /** + * Message type of the dismissed message + */ + private CeTaskMessageType ceMessageType; + /** + * Technical creation date + */ + private long createdAt; + + public UserDismissedMessageDto() { + // nothing to do here + } + + public String getUuid() { + return uuid; + } + + public UserDismissedMessageDto setUuid(String uuid) { + this.uuid = uuid; + return this; + } + + public String getUserUuid() { + return userUuid; + } + + public UserDismissedMessageDto setUserUuid(String userUuid) { + this.userUuid = userUuid; + return this; + } + + public String getProjectUuid() { + return projectUuid; + } + + public UserDismissedMessageDto setProjectUuid(String projectUuid) { + this.projectUuid = projectUuid; + return this; + } + + public CeTaskMessageType getCeMessageType() { + return ceMessageType; + } + + public UserDismissedMessageDto setCeMessageType(CeTaskMessageType ceMessageType) { + this.ceMessageType = ceMessageType; + return this; + } + + public long getCreatedAt() { + return createdAt; + } + + public UserDismissedMessageDto setCreatedAt(long createdAt) { + this.createdAt = createdAt; + return this; + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDismissedMessagesDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDismissedMessagesDao.java new file mode 100644 index 00000000000..16843bbd682 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDismissedMessagesDao.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.db.user; + +import java.util.List; +import java.util.Optional; +import org.sonar.api.utils.System2; +import org.sonar.db.Dao; +import org.sonar.db.DbSession; +import org.sonar.db.ce.CeTaskMessageType; +import org.sonar.db.project.ProjectDto; + +public class UserDismissedMessagesDao implements Dao { + private final System2 system2; + + public UserDismissedMessagesDao(System2 system2) { + this.system2 = system2; + } + + public UserDismissedMessageDto insert(DbSession session, UserDismissedMessageDto dto) { + long now = system2.now(); + mapper(session).insert(dto.setCreatedAt(now)); + return dto; + } + + public Optional selectByUserAndProjectAndMessageType(DbSession session, UserDto user, ProjectDto project, + CeTaskMessageType ceMessageType) { + return mapper(session).selectByUserUuidAndProjectUuidAndMessageType(user.getUuid(), project.getUuid(), ceMessageType.name()); + } + + public List selectByUser(DbSession session, UserDto user) { + return mapper(session).selectByUserUuid(user.getUuid()); + } + + public void deleteByUser(DbSession session, UserDto user) { + mapper(session).deleteByUserUuid(user.getUuid()); + } + + public void deleteByType(DbSession session, CeTaskMessageType type) { + mapper(session).deleteByType(type.name()); + } + + private static UserDismissedMessagesMapper mapper(DbSession session) { + return session.getMapper(UserDismissedMessagesMapper.class); + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDismissedMessagesMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDismissedMessagesMapper.java new file mode 100644 index 00000000000..f1fbddbfdf8 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDismissedMessagesMapper.java @@ -0,0 +1,37 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.db.user; + +import java.util.List; +import java.util.Optional; +import org.apache.ibatis.annotations.Param; + +public interface UserDismissedMessagesMapper { + void insert(@Param("dto") UserDismissedMessageDto dto); + + Optional selectByUserUuidAndProjectUuidAndMessageType(@Param("userUuid") String userUuid, @Param("projectUuid") String projectUuid, + @Param("ceMessageType") String ceMessageType); + + List selectByUserUuid(@Param("userUuid") String userUuid); + + void deleteByUserUuid(@Param("userUuid") String userUuid); + + void deleteByType(@Param("ceMessageType") String ceMessageType); +} diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/ce/CeTaskMessageMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/ce/CeTaskMessageMapper.xml index 1e5ca2cb2f3..360ac4425c1 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/ce/CeTaskMessageMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/ce/CeTaskMessageMapper.xml @@ -7,10 +7,19 @@ ctm.uuid, ctm.task_uuid as taskUuid, ctm.message as message, - ctm.created_at as createdAt, - ctm.is_dismissible as dismissible + ctm.message_type as type, + ctm.created_at as createdAt + + + select + + from + user_dismissed_messages udm + where + udm.user_uuid=#{userUuid,jdbcType=VARCHAR} and + udm.project_uuid=#{projectUuid,jdbcType=VARCHAR} and + udm.message_type=#{ceMessageType,jdbcType=VARCHAR} + + + + + + delete from user_dismissed_messages where user_uuid = #{userUuid, jdbcType=VARCHAR} + + + + delete from user_dismissed_messages where message_type = #{ceMessageType, jdbcType=VARCHAR} + + + diff --git a/server/sonar-db-dao/src/schema/schema-sq.ddl b/server/sonar-db-dao/src/schema/schema-sq.ddl index e31dc777fd9..e4f53071172 100644 --- a/server/sonar-db-dao/src/schema/schema-sq.ddl +++ b/server/sonar-db-dao/src/schema/schema-sq.ddl @@ -169,10 +169,11 @@ CREATE TABLE "CE_TASK_MESSAGE"( "TASK_UUID" VARCHAR(40) NOT NULL, "MESSAGE" VARCHAR(4000) NOT NULL, "CREATED_AT" BIGINT NOT NULL, - "IS_DISMISSIBLE" BOOLEAN NOT NULL + "MESSAGE_TYPE" VARCHAR(255) NOT NULL ); ALTER TABLE "CE_TASK_MESSAGE" ADD CONSTRAINT "PK_CE_TASK_MESSAGE" PRIMARY KEY("UUID"); CREATE INDEX "CE_TASK_MESSAGE_TASK" ON "CE_TASK_MESSAGE"("TASK_UUID"); +CREATE INDEX "CTM_MESSAGE_TYPE" ON "CE_TASK_MESSAGE"("MESSAGE_TYPE"); CREATE TABLE "COMPONENTS"( "UUID" VARCHAR(50) NOT NULL, @@ -896,6 +897,18 @@ ALTER TABLE "SNAPSHOTS" ADD CONSTRAINT "PK_SNAPSHOTS" PRIMARY KEY("UUID"); CREATE UNIQUE INDEX "ANALYSES_UUID" ON "SNAPSHOTS"("UUID"); CREATE INDEX "SNAPSHOT_COMPONENT" ON "SNAPSHOTS"("COMPONENT_UUID"); +CREATE TABLE "USER_DISMISSED_MESSAGES"( + "UUID" VARCHAR(40) NOT NULL, + "USER_UUID" VARCHAR(255) NOT NULL, + "PROJECT_UUID" VARCHAR(40) NOT NULL, + "MESSAGE_TYPE" VARCHAR(255) NOT NULL, + "CREATED_AT" BIGINT NOT NULL +); +ALTER TABLE "USER_DISMISSED_MESSAGES" ADD CONSTRAINT "PK_USER_DISMISSED_MESSAGES" PRIMARY KEY("UUID"); +CREATE UNIQUE INDEX "UNIQ_USER_DISMISSED_MESSAGES" ON "USER_DISMISSED_MESSAGES"("USER_UUID", "PROJECT_UUID", "MESSAGE_TYPE"); +CREATE INDEX "UDM_PROJECT_UUID" ON "USER_DISMISSED_MESSAGES"("PROJECT_UUID"); +CREATE INDEX "UDM_MESSAGE_TYPE" ON "USER_DISMISSED_MESSAGES"("MESSAGE_TYPE"); + CREATE TABLE "USER_PROPERTIES"( "UUID" VARCHAR(40) NOT NULL, "USER_UUID" VARCHAR(255) NOT NULL, diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeActivityDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeActivityDaoTest.java index d53912a6779..c6d08a72d11 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeActivityDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeActivityDaoTest.java @@ -139,6 +139,7 @@ public class CeActivityDaoTest { .setUuid(UuidFactoryFast.getInstance().create()) .setTaskUuid(task.getUuid()) .setMessage("message_" + task.getUuid() + "_" + i) + .setType(CeTaskMessageType.GENERIC) .setCreatedAt(task.getUuid().hashCode() + i))); db.commit(); } diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeTaskMessageDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeTaskMessageDaoTest.java index 149a81734cf..4ad7e016454 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeTaskMessageDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeTaskMessageDaoTest.java @@ -20,6 +20,7 @@ package org.sonar.db.ce; import java.util.List; +import java.util.Optional; import org.assertj.core.groups.Tuple; import org.junit.Rule; import org.junit.Test; @@ -37,7 +38,7 @@ public class CeTaskMessageDaoTest { @Rule public ExpectedException expectedException = ExpectedException.none(); - private CeTaskMessageDao underTest = new CeTaskMessageDao(); + private final CeTaskMessageDao underTest = new CeTaskMessageDao(); @Test public void insert() { @@ -45,13 +46,17 @@ public class CeTaskMessageDaoTest { .setUuid("uuid_1") .setTaskUuid("task_uuid_1") .setMessage("message_1") + .setType(CeTaskMessageType.GENERIC) .setCreatedAt(1_222_333L)); dbTester.getSession().commit(); - assertThat(dbTester.select("select uuid as \"UUID\", task_uuid as \"TASK_UUID\", message as \"MESSAGE\", created_at as \"CREATED_AT\" from ce_task_message")) - .hasSize(1) - .extracting(t -> t.get("UUID"), t -> t.get("TASK_UUID"), t -> t.get("MESSAGE"), t -> t.get("CREATED_AT")) - .containsOnly(Tuple.tuple("uuid_1", "task_uuid_1", "message_1", 1_222_333L)); + assertThat( + dbTester.select("select uuid as \"UUID\", task_uuid as \"TASK_UUID\", message as \"MESSAGE\", message_type as \"TYPE\", " + + "created_at as \"CREATED_AT\" from ce_task_message")) + .hasSize(1) + .extracting(t -> t.get("UUID"), t -> t.get("TASK_UUID"), t -> t.get("MESSAGE"), t -> CeTaskMessageType.valueOf((String) t.get("TYPE")), + t -> t.get("CREATED_AT")) + .containsOnly(Tuple.tuple("uuid_1", "task_uuid_1", "message_1", CeTaskMessageType.GENERIC, 1_222_333L)); } @Test @@ -88,11 +93,50 @@ public class CeTaskMessageDaoTest { .isEmpty(); } + @Test + public void selectByUuid_returns_object_if_found() { + CeTaskMessageDto dto = insertMessage("526787a4-e8af-46c0-b340-8c48188646a5", 1, 1_222_333L); + + Optional result = underTest.selectByUuid(dbTester.getSession(), dto.getUuid()); + + assertThat(result).isPresent(); + assertThat(result.get().getUuid()).isEqualTo(dto.getUuid()); + } + + @Test + public void selectByUuid_returns_empty_if_no_record_found() { + Optional result = underTest.selectByUuid(dbTester.getSession(), "e2a71626-1f07-402a-aac7-dd4e0bbb4394"); + + assertThat(result).isNotPresent(); + } + + @Test + public void deleteByType_deletes_messages_of_given_type() { + String task1 = "task1"; + CeTaskMessageDto[] messages = { + insertMessage(task1, 0, 1_222_333L, CeTaskMessageType.GENERIC), + insertMessage(task1, 1, 2_222_333L, CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE), + insertMessage(task1, 2, 1_111_333L, CeTaskMessageType.GENERIC), + insertMessage(task1, 3, 1_222_111L, CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE) + }; + + underTest.deleteByType(dbTester.getSession(), CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); + + assertThat(underTest.selectByTask(dbTester.getSession(), task1)) + .extracting(CeTaskMessageDto::getUuid) + .containsExactlyInAnyOrder(messages[0].getUuid(), messages[2].getUuid()); + } + private CeTaskMessageDto insertMessage(String taskUuid, int i, long createdAt) { + return insertMessage(taskUuid, i, createdAt, CeTaskMessageType.GENERIC); + } + + private CeTaskMessageDto insertMessage(String taskUuid, int i, long createdAt, CeTaskMessageType messageType) { CeTaskMessageDto res = new CeTaskMessageDto() .setUuid("message_" + i) .setTaskUuid(taskUuid) .setMessage("test_" + i) + .setType(messageType) .setCreatedAt(createdAt); DbSession dbSession = dbTester.getSession(); underTest.insert(dbSession, res); diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeCommandsTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeCommandsTest.java index 596f943d0a2..020cb201687 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeCommandsTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeCommandsTest.java @@ -36,6 +36,7 @@ import org.sonar.api.impl.utils.AlwaysIncreasingSystem2; import org.sonar.api.utils.System2; import org.sonar.core.util.UuidFactoryFast; import org.sonar.db.DbTester; +import org.sonar.db.ce.CeTaskMessageType; import org.sonar.db.component.BranchDto; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentTesting; @@ -625,6 +626,22 @@ public class PurgeCommandsTest { assertThat(dbTester.countRowsOfTable("new_code_periods")).isEqualTo(3); } + @Test + public void deleteUserDismissedMessages_deletes_dismissed_warnings_on_project_for_all_users() { + UserDto user1 = dbTester.users().insertUser(); + UserDto user2 = dbTester.users().insertUser(); + ProjectDto project = dbTester.components().insertPrivateProjectDto(); + ProjectDto anotherProject = dbTester.components().insertPrivateProjectDto(); + dbTester.users().insertUserDismissedMessage(user1, project, CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); + dbTester.users().insertUserDismissedMessage(user2, project, CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); + dbTester.users().insertUserDismissedMessage(user1, anotherProject, CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); + PurgeCommands purgeCommands = new PurgeCommands(dbTester.getSession(), profiler, system2); + + purgeCommands.deleteUserDismissedMessages(project.getUuid()); + + assertThat(dbTester.countRowsOfTable("user_dismissed_messages")).isEqualTo(1); + } + private void addPermissions(OrganizationDto organization, ComponentDto root) { if (!root.isPrivate()) { dbTester.users().insertProjectPermissionOnAnyone("foo1", root); diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java index e6a9ef93314..cf3dcdb411d 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java @@ -47,7 +47,6 @@ import org.sonar.core.util.Uuids; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.DbTester; -import org.sonar.db.alm.ALM; import org.sonar.db.alm.setting.AlmSettingDto; import org.sonar.db.ce.CeActivityDto; import org.sonar.db.ce.CeQueueDto; @@ -55,6 +54,7 @@ import org.sonar.db.ce.CeQueueDto.Status; import org.sonar.db.ce.CeTaskCharacteristicDto; import org.sonar.db.ce.CeTaskInputDao; import org.sonar.db.ce.CeTaskMessageDto; +import org.sonar.db.ce.CeTaskMessageType; import org.sonar.db.ce.CeTaskTypes; import org.sonar.db.component.BranchDto; import org.sonar.db.component.BranchType; @@ -78,6 +78,8 @@ import org.sonar.db.project.ProjectDto; import org.sonar.db.property.PropertyDto; import org.sonar.db.rule.RuleDefinitionDto; import org.sonar.db.source.FileSourceDto; +import org.sonar.db.user.UserDismissedMessageDto; +import org.sonar.db.user.UserDto; import org.sonar.db.webhook.WebhookDeliveryLiteDto; import org.sonar.db.webhook.WebhookDto; @@ -110,16 +112,16 @@ public class PurgeDaoTest { private static final String PROJECT_UUID = "P1"; - private System2 system2 = mock(System2.class); + private final System2 system2 = mock(System2.class); @Rule public DbTester db = DbTester.create(system2); @Rule public ExpectedException expectedException = ExpectedException.none(); - private DbClient dbClient = db.getDbClient(); - private DbSession dbSession = db.getSession(); - private PurgeDao underTest = db.getDbClient().purgeDao(); + private final DbClient dbClient = db.getDbClient(); + private final DbSession dbSession = db.getSession(); + private final PurgeDao underTest = db.getDbClient().purgeDao(); @Test public void purge_failed_ce_tasks() { @@ -702,6 +704,25 @@ public class PurgeDaoTest { assertThat(taskUuidsIn("ce_task_message")).containsOnly(anotherProjectTask.getUuid(), "non existing task"); } + @Test + public void delete_rows_in_user_dismissed_messages_when_deleting_project() { + UserDto user1 = db.users().insertUser(); + UserDto user2 = db.users().insertUser(); + ProjectDto project = db.components().insertPrivateProjectDto(); + ProjectDto anotherProject = db.components().insertPrivateProjectDto(); + + UserDismissedMessageDto msg1 = db.users().insertUserDismissedMessage(user1, project, CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); + UserDismissedMessageDto msg2 = db.users().insertUserDismissedMessage(user2, project, CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); + UserDismissedMessageDto msg3 = db.users().insertUserDismissedMessage(user1, anotherProject, CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); + + assertThat(uuidsIn("user_dismissed_messages")).containsOnly(msg1.getUuid(), msg2.getUuid(), msg3.getUuid()); + + underTest.deleteProject(dbSession, project.getUuid()); + dbSession.commit(); + + assertThat(uuidsIn("user_dismissed_messages")).containsOnly(msg3.getUuid()); + } + @Test public void delete_tasks_in_ce_queue_when_deleting_project() { ComponentDto projectToBeDeleted = db.components().insertPrivateProject(); @@ -1629,6 +1650,7 @@ public class PurgeDaoTest { .setUuid(UuidFactoryFast.getInstance().create()) .setTaskUuid(uuid) .setMessage("key_" + uuid.hashCode() + i) + .setType(CeTaskMessageType.GENERIC) .setCreatedAt(2_333_444L + i)) .forEach(dto -> dbClient.ceTaskMessageDao().insert(dbSession, dto)); dbSession.commit(); diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDismissedMessagesDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDismissedMessagesDaoTest.java new file mode 100644 index 00000000000..e7b640ab0bd --- /dev/null +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDismissedMessagesDaoTest.java @@ -0,0 +1,152 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.db.user; + +import java.util.List; +import java.util.Optional; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.core.util.Uuids; +import org.sonar.db.DbTester; +import org.sonar.db.ce.CeTaskMessageType; +import org.sonar.db.project.ProjectDto; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static org.sonar.db.ce.CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE; + +public class UserDismissedMessagesDaoTest { + + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + + private final UserDismissedMessagesDao underTest = db.getDbClient().userDismissedMessagesDao(); + + @Test + public void insert_user_dismissed_message() { + ProjectDto project = db.components().insertPrivateProjectDto(); + UserDto user = db.users().insertUser(); + UserDismissedMessageDto dto = newDto(project, user); + + underTest.insert(db.getSession(), dto); + + Optional dtoFromDb = underTest.selectByUserAndProjectAndMessageType(db.getSession(), user, project, dto.getCeMessageType()); + assertThat(dtoFromDb).isPresent(); + assertThat(dtoFromDb.get().getUuid()).isEqualTo(dto.getUuid()); + assertThat(dtoFromDb.get().getUserUuid()).isEqualTo(dto.getUserUuid()); + assertThat(dtoFromDb.get().getProjectUuid()).isEqualTo(dto.getProjectUuid()); + assertThat(dtoFromDb.get().getCeMessageType()).isEqualTo(dto.getCeMessageType()); + assertThat(dtoFromDb.get().getCeMessageType().isDismissible()).isEqualTo(dto.getCeMessageType().isDismissible()); + assertThat(dtoFromDb.get().getCreatedAt()).isEqualTo(dto.getCreatedAt()); + } + + @Test + public void selectByUserAndProjectAndMessageType_returns_object_if_record_found() { + UserDto user = db.users().insertUser(); + ProjectDto project = db.components().insertPrivateProjectDto(); + db.users().insertUserDismissedMessage(user, project, CeTaskMessageType.GENERIC); + + Optional result = underTest.selectByUserAndProjectAndMessageType(db.getSession(), user, project, CeTaskMessageType.GENERIC); + + assertThat(result).isPresent(); + assertThat(result.get().getUserUuid()).isEqualTo(user.getUuid()); + assertThat(result.get().getProjectUuid()).isEqualTo(project.getUuid()); + assertThat(result.get().getCeMessageType()).isEqualTo(CeTaskMessageType.GENERIC); + } + + @Test + public void selectByUserAndProjectAndMessageType_returns_absent_if_no_record_found() { + UserDto user = db.users().insertUser(); + ProjectDto project = db.components().insertPrivateProjectDto(); + db.users().insertUserDismissedMessage(user, project, CeTaskMessageType.GENERIC); + + Optional result = underTest.selectByUserAndProjectAndMessageType(db.getSession(), user, project, SUGGEST_DEVELOPER_EDITION_UPGRADE); + + assertThat(result).isNotPresent(); + } + + @Test + public void selectByUserUuid_returns_all_dismissed_messages_of_a_user() { + UserDto user1 = db.users().insertUser(); + UserDto user2 = db.users().insertUser(); + ProjectDto project1 = db.components().insertPrivateProjectDto(); + ProjectDto project2 = db.components().insertPrivateProjectDto(); + db.users().insertUserDismissedMessage(user1, project1, CeTaskMessageType.GENERIC); + db.users().insertUserDismissedMessage(user1, project2, CeTaskMessageType.GENERIC); + UserDismissedMessageDto dto1 = db.users().insertUserDismissedMessage(user2, project1, CeTaskMessageType.GENERIC); + UserDismissedMessageDto dto2 = db.users().insertUserDismissedMessage(user2, project2, CeTaskMessageType.GENERIC); + + List result = underTest.selectByUser(db.getSession(), user2); + + assertThat(result).hasSize(2); + assertThat(result).extracting(UserDismissedMessageDto::getUuid, UserDismissedMessageDto::getUserUuid, UserDismissedMessageDto::getProjectUuid, + UserDismissedMessageDto::getCeMessageType) + .containsExactlyInAnyOrder( + tuple(dto1.getUuid(), user2.getUuid(), project1.getUuid(), CeTaskMessageType.GENERIC), + tuple(dto2.getUuid(), user2.getUuid(), project2.getUuid(), CeTaskMessageType.GENERIC)); + } + + @Test + public void deleteByUserUuid_removes_dismiss_warning_data_of_a_user() { + UserDto user1 = db.users().insertUser(); + UserDto user2 = db.users().insertUser(); + ProjectDto project1 = db.components().insertPrivateProjectDto(); + ProjectDto project2 = db.components().insertPrivateProjectDto(); + db.users().insertUserDismissedMessage(user1, project1, CeTaskMessageType.GENERIC); + db.users().insertUserDismissedMessage(user1, project2, CeTaskMessageType.GENERIC); + db.users().insertUserDismissedMessage(user2, project1, CeTaskMessageType.GENERIC); + db.users().insertUserDismissedMessage(user2, project2, CeTaskMessageType.GENERIC); + + underTest.deleteByUser(db.getSession(), user2); + + assertThat(underTest.selectByUser(db.getSession(), user1)).hasSize(2); + assertThat(underTest.selectByUser(db.getSession(), user2)).isEmpty(); + } + + @Test + public void deleteByUserUuid_removes_dismissed_messages_of_that_type() { + UserDto user1 = db.users().insertUser(); + UserDto user2 = db.users().insertUser(); + ProjectDto project1 = db.components().insertPrivateProjectDto(); + ProjectDto project2 = db.components().insertPrivateProjectDto(); + UserDismissedMessageDto dto1 = db.users().insertUserDismissedMessage(user1, project1, CeTaskMessageType.GENERIC); + db.users().insertUserDismissedMessage(user1, project2, SUGGEST_DEVELOPER_EDITION_UPGRADE); + db.users().insertUserDismissedMessage(user2, project1, SUGGEST_DEVELOPER_EDITION_UPGRADE); + UserDismissedMessageDto dto2 = db.users().insertUserDismissedMessage(user2, project2, CeTaskMessageType.GENERIC); + + underTest.deleteByType(db.getSession(), SUGGEST_DEVELOPER_EDITION_UPGRADE); + + assertThat(underTest.selectByUser(db.getSession(), user1)) + .extracting(UserDismissedMessageDto::getUuid) + .containsExactly(dto1.getUuid()); + assertThat(underTest.selectByUser(db.getSession(), user2)) + .extracting(UserDismissedMessageDto::getUuid) + .containsExactly(dto2.getUuid()); + } + + public static UserDismissedMessageDto newDto(ProjectDto project, UserDto user) { + return new UserDismissedMessageDto() + .setUuid(Uuids.createFast()) + .setCeMessageType(CeTaskMessageType.GENERIC) + .setUserUuid(user.getUuid()) + .setProjectUuid(project.getUuid()); + } +} diff --git a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserDbTester.java b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserDbTester.java index 079e15fe109..d3b3d4540e3 100644 --- a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserDbTester.java +++ b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserDbTester.java @@ -32,11 +32,13 @@ import org.sonar.core.util.Uuids; import org.sonar.core.util.stream.MoreCollectors; import org.sonar.db.DbClient; import org.sonar.db.DbTester; +import org.sonar.db.ce.CeTaskMessageType; import org.sonar.db.component.ComponentDto; import org.sonar.db.organization.OrganizationDto; import org.sonar.db.permission.GroupPermissionDto; import org.sonar.db.permission.OrganizationPermission; import org.sonar.db.permission.UserPermissionDto; +import org.sonar.db.project.ProjectDto; import static com.google.common.base.Preconditions.checkArgument; import static java.lang.String.format; @@ -403,4 +405,15 @@ public class UserDbTester { return dto; } + public final UserDismissedMessageDto insertUserDismissedMessage(UserDto userDto, ProjectDto projectDto, CeTaskMessageType messageType) { + UserDismissedMessageDto dto = new UserDismissedMessageDto() + .setUuid(Uuids.create()) + .setUserUuid(userDto.getUuid()) + .setProjectUuid(projectDto.getUuid()) + .setCeMessageType(messageType); + db.getDbClient().userDismissedMessagesDao().insert(db.getSession(), dto); + db.commit(); + return dto; + } + } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/AddIndexOnMessageTypeColumnOfCeTaskMessageTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/AddIndexOnMessageTypeColumnOfCeTaskMessageTable.java new file mode 100644 index 00000000000..091289b5cc5 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/AddIndexOnMessageTypeColumnOfCeTaskMessageTable.java @@ -0,0 +1,42 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.platform.db.migration.version.v85; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder; +import org.sonar.server.platform.db.migration.step.DdlChange; + +public class AddIndexOnMessageTypeColumnOfCeTaskMessageTable extends DdlChange { + private static final String TABLE = "ce_task_message"; + + public AddIndexOnMessageTypeColumnOfCeTaskMessageTable(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + context.execute(new CreateIndexBuilder() + .setTable(TABLE) + .setName("ctm_message_type") + .addColumn("message_type") + .build()); + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/AddIsDismissibleColumnToCeTaskMessageTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/AddMessageTypeColumnToCeTaskMessageTable.java similarity index 72% rename from server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/AddIsDismissibleColumnToCeTaskMessageTable.java rename to server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/AddMessageTypeColumnToCeTaskMessageTable.java index 674ae2f2b81..f8cf9f6c7fe 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/AddIsDismissibleColumnToCeTaskMessageTable.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/AddMessageTypeColumnToCeTaskMessageTable.java @@ -21,29 +21,29 @@ package org.sonar.server.platform.db.migration.version.v85; import java.sql.SQLException; import org.sonar.db.Database; -import org.sonar.server.platform.db.migration.def.BooleanColumnDef; +import org.sonar.server.platform.db.migration.def.VarcharColumnDef; import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder; import org.sonar.server.platform.db.migration.step.DdlChange; -import static org.sonar.server.platform.db.migration.def.BooleanColumnDef.newBooleanColumnDefBuilder; +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder; -public class AddIsDismissibleColumnToCeTaskMessageTable extends DdlChange { +public class AddMessageTypeColumnToCeTaskMessageTable extends DdlChange { private static final String TABLE = "ce_task_message"; - private static final String NEW_COLUMN = "is_dismissible"; - private static final BooleanColumnDef IS_DISMISSIBLE = newBooleanColumnDefBuilder() - .setColumnName(NEW_COLUMN) + private static final VarcharColumnDef MESSAGE_TYPE = newVarcharColumnDefBuilder() + .setColumnName("message_type") .setIsNullable(true) + .setLimit(255) .build(); - public AddIsDismissibleColumnToCeTaskMessageTable(Database db) { + public AddMessageTypeColumnToCeTaskMessageTable(Database db) { super(db); } @Override public void execute(Context context) throws SQLException { context.execute(new AddColumnsBuilder(getDialect(), TABLE) - .addColumn(IS_DISMISSIBLE) + .addColumn(MESSAGE_TYPE) .build()); } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/CreateUserDismissedMessagesTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/CreateUserDismissedMessagesTable.java new file mode 100644 index 00000000000..82b91108df1 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/CreateUserDismissedMessagesTable.java @@ -0,0 +1,102 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.platform.db.migration.version.v85; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.def.BigIntegerColumnDef; +import org.sonar.server.platform.db.migration.def.VarcharColumnDef; +import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder; +import org.sonar.server.platform.db.migration.sql.CreateTableBuilder; +import org.sonar.server.platform.db.migration.step.DdlChange; + +import static org.sonar.server.platform.db.migration.def.BigIntegerColumnDef.newBigIntegerColumnDefBuilder; +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.UUID_SIZE; +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder; + +public class CreateUserDismissedMessagesTable extends DdlChange { + + private static final String TABLE_NAME = "user_dismissed_messages"; + + private static final VarcharColumnDef UUID_COLUMN = newVarcharColumnDefBuilder() + .setColumnName("uuid") + .setIsNullable(false) + .setLimit(UUID_SIZE) + .build(); + + private static final VarcharColumnDef USER_UUID_COLUMN = newVarcharColumnDefBuilder() + .setColumnName("user_uuid") + .setIsNullable(false) + .setLimit(VarcharColumnDef.USER_UUID_SIZE) + .build(); + + private static final VarcharColumnDef PROJECT_UUID = newVarcharColumnDefBuilder() + .setColumnName("project_uuid") + .setIsNullable(false) + .setLimit(UUID_SIZE) + .build(); + + private static final VarcharColumnDef MESSAGE_TYPE = newVarcharColumnDefBuilder() + .setColumnName("message_type") + .setIsNullable(false) + .setLimit(255) + .build(); + + private static final BigIntegerColumnDef CREATED_AT = newBigIntegerColumnDefBuilder() + .setColumnName("created_at") + .setIsNullable(false) + .build(); + + public CreateUserDismissedMessagesTable(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + context.execute(new CreateTableBuilder(getDialect(), TABLE_NAME) + .addPkColumn(UUID_COLUMN) + .addColumn(USER_UUID_COLUMN) + .addColumn(PROJECT_UUID) + .addColumn(MESSAGE_TYPE) + .addColumn(CREATED_AT) + .build()); + + context.execute(new CreateIndexBuilder() + .setTable(TABLE_NAME) + .setName("uniq_user_dismissed_messages") + .setUnique(true) + .addColumn(USER_UUID_COLUMN) + .addColumn(PROJECT_UUID) + .addColumn(MESSAGE_TYPE) + .build()); + + context.execute(new CreateIndexBuilder() + .setTable(TABLE_NAME) + .setName("udm_project_uuid") + .addColumn(PROJECT_UUID) + .build()); + + context.execute(new CreateIndexBuilder() + .setTable(TABLE_NAME) + .setName("udm_message_type") + .addColumn(MESSAGE_TYPE) + .build()); + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/DbVersion85.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/DbVersion85.java index 80ea17d33da..0396ecac844 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/DbVersion85.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/DbVersion85.java @@ -47,9 +47,11 @@ public class DbVersion85 implements DbVersion { .add(4016, "Add 'type' column to 'plugins' table", AddTypeToPlugins.class) .add(4017, "Populate 'type' column in 'plugins' table", PopulateTypeInPlugins.class) .add(4018, "Alter 'type' column in 'plugins' to not nullable", AlterTypeInPluginNotNullable.class) - .add(4019, "Add 'is_dismissible' column to `ce_task_message` table", AddIsDismissibleColumnToCeTaskMessageTable.class) - .add(4020, "Populate 'is_dismissible' column of `ce_task_message` table", PopulateIsDismissibleColumnOfCeTaskMessageTable.class) - .add(4021, "Make 'is_dismissible' column not nullable for `ce_task_message` table", MakeIsDismissibleColumnNotNullableOnCeTaskMessageTable.class) - ; + .add(4019, "Add 'message_type' column to 'ce_task_message' table", AddMessageTypeColumnToCeTaskMessageTable.class) + .add(4020, "Populate 'message_type' column of 'ce_task_message' table", PopulateMessageTypeColumnOfCeTaskMessageTable.class) + .add(4021, "Make 'message_type' column not nullable for `ce_task_message` table", MakeMessageTypeColumnNotNullableOnCeTaskMessageTable.class) + .add(4022, "Add index on 'message_type' column of `ce_task_message` table", AddIndexOnMessageTypeColumnOfCeTaskMessageTable.class) + .add(4023, "Create 'user_dismissed_messages' table", CreateUserDismissedMessagesTable.class + ); } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/MakeIsDismissibleColumnNotNullableOnCeTaskMessageTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/MakeMessageTypeColumnNotNullableOnCeTaskMessageTable.java similarity index 65% rename from server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/MakeIsDismissibleColumnNotNullableOnCeTaskMessageTable.java rename to server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/MakeMessageTypeColumnNotNullableOnCeTaskMessageTable.java index 6b285203b4b..aad98f9c76a 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/MakeIsDismissibleColumnNotNullableOnCeTaskMessageTable.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/MakeMessageTypeColumnNotNullableOnCeTaskMessageTable.java @@ -21,30 +21,27 @@ package org.sonar.server.platform.db.migration.version.v85; import java.sql.SQLException; import org.sonar.db.Database; -import org.sonar.server.platform.db.migration.def.BooleanColumnDef; +import org.sonar.server.platform.db.migration.def.VarcharColumnDef; import org.sonar.server.platform.db.migration.sql.AlterColumnsBuilder; import org.sonar.server.platform.db.migration.step.DdlChange; -import static org.sonar.server.platform.db.migration.def.BooleanColumnDef.newBooleanColumnDefBuilder; +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder; -public class MakeIsDismissibleColumnNotNullableOnCeTaskMessageTable extends DdlChange { - - private static final String TABLE = "ce_task_message"; - private static final String NEW_COLUMN = "is_dismissible"; - - private static final BooleanColumnDef IS_DISMISSIBLE = newBooleanColumnDefBuilder() - .setColumnName(NEW_COLUMN) +public class MakeMessageTypeColumnNotNullableOnCeTaskMessageTable extends DdlChange { + private static final VarcharColumnDef MESSAGE_TYPE = newVarcharColumnDefBuilder() + .setColumnName("message_type") .setIsNullable(false) + .setLimit(255) .build(); - public MakeIsDismissibleColumnNotNullableOnCeTaskMessageTable(Database db) { + public MakeMessageTypeColumnNotNullableOnCeTaskMessageTable(Database db) { super(db); } @Override public void execute(Context context) throws SQLException { - context.execute(new AlterColumnsBuilder(getDialect(), TABLE) - .updateColumn(IS_DISMISSIBLE) + context.execute(new AlterColumnsBuilder(getDialect(), "ce_task_message") + .updateColumn(MESSAGE_TYPE) .build()); } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/PopulateIsDismissibleColumnOfCeTaskMessageTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/PopulateMessageTypeColumnOfCeTaskMessageTable.java similarity index 78% rename from server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/PopulateIsDismissibleColumnOfCeTaskMessageTable.java rename to server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/PopulateMessageTypeColumnOfCeTaskMessageTable.java index 63863b29912..00e358dcff7 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/PopulateIsDismissibleColumnOfCeTaskMessageTable.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/PopulateMessageTypeColumnOfCeTaskMessageTable.java @@ -24,20 +24,18 @@ import org.sonar.db.Database; import org.sonar.server.platform.db.migration.step.DataChange; import org.sonar.server.platform.db.migration.step.MassUpdate; -public class PopulateIsDismissibleColumnOfCeTaskMessageTable extends DataChange { - - public PopulateIsDismissibleColumnOfCeTaskMessageTable(Database db) { +public class PopulateMessageTypeColumnOfCeTaskMessageTable extends DataChange { + public PopulateMessageTypeColumnOfCeTaskMessageTable(Database db) { super(db); } @Override - protected void execute(Context context) throws SQLException { + protected void execute(DataChange.Context context) throws SQLException { MassUpdate massUpdate = context.prepareMassUpdate(); - massUpdate.select("select ctm.uuid from ce_task_message ctm where ctm.is_dismissible is null"); - massUpdate.update("update ce_task_message set is_dismissible = ? where uuid = ?"); - + massUpdate.select("select ctm.uuid from ce_task_message ctm where ctm.message_type is null"); + massUpdate.update("update ce_task_message set message_type = ? where uuid = ?"); massUpdate.execute((row, update) -> { - update.setBoolean(1, false); + update.setString(1, "GENERIC"); update.setString(2, row.getString(1)); return true; }); diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/AddIsDismissibleColumnToCeTaskMessageTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/AddIndexOnMessageTypeColumnOfCeTaskMessageTableTest.java similarity index 68% rename from server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/AddIsDismissibleColumnToCeTaskMessageTableTest.java rename to server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/AddIndexOnMessageTypeColumnOfCeTaskMessageTableTest.java index bc51cb03288..b5c648a79bc 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/AddIsDismissibleColumnToCeTaskMessageTableTest.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/AddIndexOnMessageTypeColumnOfCeTaskMessageTableTest.java @@ -17,7 +17,6 @@ * 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.platform.db.migration.version.v85; import java.sql.SQLException; @@ -26,20 +25,19 @@ import org.junit.Test; import org.sonar.db.CoreDbTester; import org.sonar.server.platform.db.migration.step.DdlChange; -import static java.sql.Types.BOOLEAN; - -public class AddIsDismissibleColumnToCeTaskMessageTableTest { +public class AddIndexOnMessageTypeColumnOfCeTaskMessageTableTest { + private static final String TABLE_NAME = "ce_task_message"; + private static final String INDEX_NAME = "ctm_message_type"; @Rule - public CoreDbTester db = CoreDbTester.createForSchema(AddIsDismissibleColumnToCeTaskMessageTableTest.class, "schema.sql"); + public CoreDbTester db = CoreDbTester.createForSchema(AddIndexOnMessageTypeColumnOfCeTaskMessageTableTest.class, "schema.sql"); - DdlChange underTest = new AddIsDismissibleColumnToCeTaskMessageTable(db.database()); + DdlChange underTest = new AddIndexOnMessageTypeColumnOfCeTaskMessageTable(db.database()); @Test - public void add_column() throws SQLException { + public void add_index() throws SQLException { underTest.execute(); - db.assertColumnDefinition("ce_task_message", "is_dismissible", BOOLEAN, null, true); + db.assertIndex(TABLE_NAME, INDEX_NAME, "message_type"); } - } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/AddMessageTypeColumnToCeTaskMessageTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/AddMessageTypeColumnToCeTaskMessageTableTest.java new file mode 100644 index 00000000000..0ab65645f12 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/AddMessageTypeColumnToCeTaskMessageTableTest.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.platform.db.migration.version.v85; + +import java.sql.SQLException; +import java.sql.Types; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.db.CoreDbTester; +import org.sonar.server.platform.db.migration.step.DdlChange; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AddMessageTypeColumnToCeTaskMessageTableTest { + + @Rule + public CoreDbTester db = CoreDbTester.createForSchema(AddMessageTypeColumnToCeTaskMessageTableTest.class, "schema.sql"); + + private final DdlChange underTest = new AddMessageTypeColumnToCeTaskMessageTable(db.database()); + + @Before + public void setup() { + insertTaskMessage(1L, "a1"); + insertTaskMessage(2L, "a2"); + insertTaskMessage(3L, "a3"); + } + + @Test + public void add_message_type_column_to_ce_task_message_table() throws SQLException { + underTest.execute(); + + db.assertColumnDefinition("ce_task_message", "message_type", Types.VARCHAR, 255, true); + + assertThat(db.countSql("select count(uuid) from ce_task_message")).isEqualTo(3); + } + + private void insertTaskMessage(Long id, String message) { + db.executeInsert("ce_task_message", + "uuid", "uuid-" + id, + "task_uuid", "task-uuid-" + id, + "message", message, + "created_at", System2.INSTANCE.now()); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/CreateUserDismissedMessagesTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/CreateUserDismissedMessagesTableTest.java new file mode 100644 index 00000000000..427fda8f9c4 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/CreateUserDismissedMessagesTableTest.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.platform.db.migration.version.v85; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.db.CoreDbTester; + +import static java.sql.Types.BIGINT; +import static java.sql.Types.VARCHAR; + +public class CreateUserDismissedMessagesTableTest { + private static final String TABLE_NAME = "user_dismissed_messages"; + + @Rule + public CoreDbTester dbTester = CoreDbTester.createEmpty(); + + private final CreateUserDismissedMessagesTable underTest = new CreateUserDismissedMessagesTable(dbTester.database()); + + @Test + public void table_has_been_created() throws SQLException { + underTest.execute(); + + dbTester.assertTableExists(TABLE_NAME); + dbTester.assertPrimaryKey(TABLE_NAME, "pk_user_dismissed_messages", "uuid"); + dbTester.assertColumnDefinition(TABLE_NAME, "uuid", VARCHAR, 40, false); + dbTester.assertColumnDefinition(TABLE_NAME, "user_uuid", VARCHAR, 255, false); + dbTester.assertColumnDefinition(TABLE_NAME, "project_uuid", VARCHAR, 40, false); + dbTester.assertColumnDefinition(TABLE_NAME, "message_type", VARCHAR, 255, false); + dbTester.assertColumnDefinition(TABLE_NAME, "created_at", BIGINT, null, false); + dbTester.assertUniqueIndex(TABLE_NAME, "uniq_user_dismissed_messages", "user_uuid", + "project_uuid", "message_type"); + dbTester.assertIndex(TABLE_NAME, "udm_project_uuid", "project_uuid"); + dbTester.assertIndex(TABLE_NAME, "udm_message_type", "message_type"); + } + +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/MakeIsDismissibleColumnNotNullableOnCeTaskMessageTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/MakeMessageTypeColumnNotNullableOnCeTaskMessageTableTest.java similarity index 68% rename from server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/MakeIsDismissibleColumnNotNullableOnCeTaskMessageTableTest.java rename to server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/MakeMessageTypeColumnNotNullableOnCeTaskMessageTableTest.java index 5f49ab61888..b559adf60ef 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/MakeIsDismissibleColumnNotNullableOnCeTaskMessageTableTest.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/MakeMessageTypeColumnNotNullableOnCeTaskMessageTableTest.java @@ -25,20 +25,19 @@ import org.junit.Test; import org.sonar.db.CoreDbTester; import org.sonar.server.platform.db.migration.step.MigrationStep; -import static java.sql.Types.BOOLEAN; +import static java.sql.Types.VARCHAR; -public class MakeIsDismissibleColumnNotNullableOnCeTaskMessageTableTest { +public class MakeMessageTypeColumnNotNullableOnCeTaskMessageTableTest { @Rule - public CoreDbTester db = CoreDbTester.createForSchema(MakeIsDismissibleColumnNotNullableOnCeTaskMessageTableTest.class, "schema.sql"); + public CoreDbTester db = CoreDbTester.createForSchema(MakeMessageTypeColumnNotNullableOnCeTaskMessageTableTest.class, "schema.sql"); - private MigrationStep underTest = new MakeIsDismissibleColumnNotNullableOnCeTaskMessageTable(db.database()); + private final MigrationStep underTest = new MakeMessageTypeColumnNotNullableOnCeTaskMessageTable(db.database()); @Test - public void ce_task_message_column_is_not_null() throws SQLException { + public void message_type_column_is_not_null() throws SQLException { underTest.execute(); - db.assertColumnDefinition("ce_task_message", "is_dismissible", BOOLEAN, null, false); + db.assertColumnDefinition("ce_task_message", "message_type", VARCHAR, 255, false); } - } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/PopulateIsDismissibleColumnOfCeTaskMessageTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/PopulateIsDismissibleColumnOfCeTaskMessageTableTest.java deleted file mode 100644 index fb17fc524e8..00000000000 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/PopulateIsDismissibleColumnOfCeTaskMessageTableTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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.platform.db.migration.version.v85; - -import java.sql.SQLException; -import javax.annotation.Nullable; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.core.util.Uuids; -import org.sonar.db.CoreDbTester; - -import static org.assertj.core.api.Assertions.assertThat; - -public class PopulateIsDismissibleColumnOfCeTaskMessageTableTest { - - @Rule - public CoreDbTester db = CoreDbTester.createForSchema(PopulateIsDismissibleColumnOfCeTaskMessageTableTest.class, "schema.sql"); - - private PopulateIsDismissibleColumnOfCeTaskMessageTable underTest = new PopulateIsDismissibleColumnOfCeTaskMessageTable(db.database()); - - @Test - public void execute_migration() throws SQLException { - insertCeTaskMessage(null); - insertCeTaskMessage(null); - insertCeTaskMessage(null); - - underTest.execute(); - - assertIsDismissibleValuesAreAllFalse(); - } - - @Test - public void migrate_not_already_updated_rows() throws SQLException { - insertCeTaskMessage(false); - insertCeTaskMessage(false); - insertCeTaskMessage(null); - - underTest.execute(); - - assertIsDismissibleValuesAreAllFalse(); - } - - @Test - public void migration_is_reentrant() throws SQLException { - insertCeTaskMessage(null); - - underTest.execute(); - underTest.execute(); - - assertIsDismissibleValuesAreAllFalse(); - } - - private void assertIsDismissibleValuesAreAllFalse() { - assertThat(db.select("select is_dismissible as \"IS_DISMISSIBLE\" from ce_task_message") - .stream() - .map(rows -> rows.get("IS_DISMISSIBLE"))) - .containsOnly(false); - } - - private String insertCeTaskMessage(@Nullable Boolean isDissmisible) { - String uuid = Uuids.createFast(); - db.executeInsert("CE_TASK_MESSAGE", - "UUID", uuid, - "TASK_UUID", Uuids.createFast(), - "MESSAGE", "message-" + uuid, - "IS_DISMISSIBLE", isDissmisible, - "CREATED_AT", System.currentTimeMillis()); - return uuid; - } -} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/PopulateMessageTypeColumnOfCeTaskMessageTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/PopulateMessageTypeColumnOfCeTaskMessageTableTest.java new file mode 100644 index 00000000000..b5cfcec501d --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/PopulateMessageTypeColumnOfCeTaskMessageTableTest.java @@ -0,0 +1,73 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.platform.db.migration.version.v85; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.db.CoreDbTester; +import org.sonar.server.platform.db.migration.step.DataChange; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; + +public class PopulateMessageTypeColumnOfCeTaskMessageTableTest { + + @Rule + public CoreDbTester db = CoreDbTester.createForSchema(PopulateMessageTypeColumnOfCeTaskMessageTableTest.class, "schema.sql"); + + private final DataChange underTest = new PopulateMessageTypeColumnOfCeTaskMessageTable(db.database()); + + @Test + public void add_message_type_column_on_empty_table() throws SQLException { + underTest.execute(); + + assertThat(db.countSql("select count(*) from ce_task_message")).isZero(); + } + + @Test + public void add_message_type_column_on_non_empty_table() throws SQLException { + insertTaskMessage(1L, "msg1"); + insertTaskMessage(2L, "msg2"); + insertTaskMessage(3L, "msg3"); + insertTaskMessage(4L, "msg4"); + + underTest.execute(); + + assertThat(db.countSql("select count(*) from ce_task_message")).isEqualTo(4); + assertThat(db.select("select uuid, task_uuid, message, message_type from ce_task_message")) + .extracting(m -> m.get("UUID"), m -> m.get("TASK_UUID"), m -> m.get("MESSAGE"), m -> m.get("MESSAGE_TYPE")) + .containsExactlyInAnyOrder( + tuple("uuid-1", "task-uuid-1", "msg1", "GENERIC"), + tuple("uuid-2", "task-uuid-2", "msg2", "GENERIC"), + tuple("uuid-3", "task-uuid-3", "msg3", "GENERIC"), + tuple("uuid-4", "task-uuid-4", "msg4", "GENERIC")); + } + + private void insertTaskMessage(Long id, String message) { + db.executeInsert("ce_task_message", + "uuid", "uuid-" + id, + "task_uuid", "task-uuid-" + id, + "message", message, + "created_at", System2.INSTANCE.now()); + } + +} diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/AddIndexOnMessageTypeColumnOfCeTaskMessageTableTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/AddIndexOnMessageTypeColumnOfCeTaskMessageTableTest/schema.sql new file mode 100644 index 00000000000..4200aab9747 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/AddIndexOnMessageTypeColumnOfCeTaskMessageTableTest/schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE "CE_TASK_MESSAGE"( + "UUID" VARCHAR(40) NOT NULL, + "TASK_UUID" VARCHAR(40) NOT NULL, + "MESSAGE" VARCHAR(4000) NOT NULL, + "CREATED_AT" BIGINT NOT NULL, + "MESSAGE_TYPE" VARCHAR(255) NOT NULL +); +ALTER TABLE "CE_TASK_MESSAGE" ADD CONSTRAINT "PK_CE_TASK_MESSAGE" PRIMARY KEY("UUID"); +CREATE INDEX "CE_TASK_MESSAGE_TASK" ON "CE_TASK_MESSAGE"("TASK_UUID"); diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/AddMessageTypeColumnToCeTaskMessageTableTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/AddMessageTypeColumnToCeTaskMessageTableTest/schema.sql new file mode 100644 index 00000000000..67237e59da5 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/AddMessageTypeColumnToCeTaskMessageTableTest/schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE "CE_TASK_MESSAGE" ( + "UUID" VARCHAR(40) NOT NULL, + "TASK_UUID" VARCHAR(40) NOT NULL, + "MESSAGE" VARCHAR(4000) NOT NULL, + "CREATED_AT" BIGINT NOT NULL, + + CONSTRAINT "CE_TASK_MESSAGE" PRIMARY KEY ("UUID") +); +CREATE INDEX "CE_TASK_MESSAGE_TASK" ON "CE_TASK_MESSAGE" ("TASK_UUID"); diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/MakeMessageTypeColumnNotNullableOnCeTaskMessageTableTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/MakeMessageTypeColumnNotNullableOnCeTaskMessageTableTest/schema.sql new file mode 100644 index 00000000000..c46fbbfd8c6 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/MakeMessageTypeColumnNotNullableOnCeTaskMessageTableTest/schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE "CE_TASK_MESSAGE"( + "UUID" VARCHAR(40) NOT NULL, + "TASK_UUID" VARCHAR(40) NOT NULL, + "MESSAGE" VARCHAR(4000) NOT NULL, + "CREATED_AT" BIGINT NOT NULL, + "MESSAGE_TYPE" VARCHAR(255) +); +ALTER TABLE "CE_TASK_MESSAGE" ADD CONSTRAINT "PK_CE_TASK_MESSAGE" PRIMARY KEY("UUID"); +CREATE INDEX "CE_TASK_MESSAGE_TASK" ON "CE_TASK_MESSAGE"("TASK_UUID"); diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/PopulateMessageTypeColumnOfCeTaskMessageTableTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/PopulateMessageTypeColumnOfCeTaskMessageTableTest/schema.sql new file mode 100644 index 00000000000..c46fbbfd8c6 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/PopulateMessageTypeColumnOfCeTaskMessageTableTest/schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE "CE_TASK_MESSAGE"( + "UUID" VARCHAR(40) NOT NULL, + "TASK_UUID" VARCHAR(40) NOT NULL, + "MESSAGE" VARCHAR(4000) NOT NULL, + "CREATED_AT" BIGINT NOT NULL, + "MESSAGE_TYPE" VARCHAR(255) +); +ALTER TABLE "CE_TASK_MESSAGE" ADD CONSTRAINT "PK_CE_TASK_MESSAGE" PRIMARY KEY("UUID"); +CREATE INDEX "CE_TASK_MESSAGE_TASK" ON "CE_TASK_MESSAGE"("TASK_UUID"); diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/startup/UpgradeSuggestionsCleaner.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/startup/UpgradeSuggestionsCleaner.java new file mode 100644 index 00000000000..335eed30863 --- /dev/null +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/startup/UpgradeSuggestionsCleaner.java @@ -0,0 +1,70 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.startup; + +import org.picocontainer.Startable; +import org.sonar.api.SonarEdition; +import org.sonar.api.SonarRuntime; +import org.sonar.api.server.ServerSide; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.ce.CeTaskMessageType; + +/** + * Clean up messages (like removing upgrade suggestions after an edition upgrade) + */ +@ServerSide +public class UpgradeSuggestionsCleaner implements Startable { + + private static final Logger LOGGER = Loggers.get(UpgradeSuggestionsCleaner.class); + + private final DbClient dbClient; + private final SonarRuntime sonarRuntime; + + public UpgradeSuggestionsCleaner(DbClient dbClient, SonarRuntime sonarRuntime) { + this.dbClient = dbClient; + this.sonarRuntime = sonarRuntime; + } + + @Override + public void start() { + if (sonarRuntime.getEdition() == SonarEdition.COMMUNITY) { + return; + } + + deleteUpgradeMessageDismissals(); + } + + private void deleteUpgradeMessageDismissals() { + LOGGER.info("Dismissed messages cleanup"); + try (DbSession dbSession = dbClient.openSession(false)) { + dbClient.userDismissedMessagesDao().deleteByType(dbSession, CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); + dbClient.ceTaskMessageDao().deleteByType(dbSession, CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); + dbSession.commit(); + } + } + + @Override + public void stop() { + // nothing to do + } +} diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/UpgradeSuggestionsCleanerTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/UpgradeSuggestionsCleanerTest.java new file mode 100644 index 00000000000..c5c8a032776 --- /dev/null +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/UpgradeSuggestionsCleanerTest.java @@ -0,0 +1,148 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.startup; + +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.sonar.api.SonarEdition; +import org.sonar.api.SonarRuntime; +import org.sonar.api.utils.System2; +import org.sonar.db.DbTester; +import org.sonar.db.ce.CeTaskMessageDto; +import org.sonar.db.ce.CeTaskMessageType; +import org.sonar.db.user.UserDismissedMessageDto; +import org.sonar.db.user.UserDto; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(DataProviderRunner.class) +public class UpgradeSuggestionsCleanerTest { + private static final String TASK_UUID = "b8d564dd-4ceb-4dba-8a3d-5fafa2d72cdf"; + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + private final SonarRuntime sonarRuntime = mock(SonarRuntime.class); + private final UpgradeSuggestionsCleaner underTest = new UpgradeSuggestionsCleaner(dbTester.getDbClient(), sonarRuntime); + + private UserDto user; + + @Before + public void setup() { + user = dbTester.users().insertUser(); + } + + @DataProvider + public static Object[][] editionsWithCleanup() { + return new Object[][] { + {SonarEdition.DEVELOPER}, + {SonarEdition.ENTERPRISE}, + {SonarEdition.DATACENTER} + }; + } + + @DataProvider + public static Object[][] allEditions() { + return new Object[][] { + {SonarEdition.COMMUNITY}, + {SonarEdition.DEVELOPER}, + {SonarEdition.ENTERPRISE}, + {SonarEdition.DATACENTER} + }; + } + + @Test + @UseDataProvider("editionsWithCleanup") + public void start_cleans_up_obsolete_upgrade_suggestions(SonarEdition edition) { + when(sonarRuntime.getEdition()).thenReturn(edition); + insertCeTaskMessage("ctm1", CeTaskMessageType.GENERIC, "msg1"); + insertCeTaskMessage("ctm2", CeTaskMessageType.GENERIC, "msg2"); + insertCeTaskMessage("ctm3", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE, "upgrade-msg-1"); + insertInUserDismissedMessages("u1", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); + insertInUserDismissedMessages("u2", CeTaskMessageType.GENERIC); + + underTest.start(); + underTest.stop(); + + assertThat(dbTester.getDbClient().ceTaskMessageDao().selectByTask(dbTester.getSession(), TASK_UUID)) + .extracting(CeTaskMessageDto::getUuid) + .containsExactly("ctm1", "ctm2"); + assertThat(dbTester.getDbClient().userDismissedMessagesDao().selectByUser(dbTester.getSession(), user)) + .extracting(UserDismissedMessageDto::getUuid) + .containsExactly("u2"); + } + + @Test + public void start_does_nothing_in_community_edition() { + when(sonarRuntime.getEdition()).thenReturn(SonarEdition.COMMUNITY); + insertCeTaskMessage("ctm1", CeTaskMessageType.GENERIC, "msg1"); + insertCeTaskMessage("ctm2", CeTaskMessageType.GENERIC, "msg2"); + insertCeTaskMessage("ctm3", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE, "upgrade-msg-1"); + insertInUserDismissedMessages("u1", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); + insertInUserDismissedMessages("u2", CeTaskMessageType.GENERIC); + + underTest.start(); + + assertThat(dbTester.getDbClient().ceTaskMessageDao().selectByTask(dbTester.getSession(), TASK_UUID)) + .extracting(CeTaskMessageDto::getUuid) + .containsExactly("ctm1", "ctm2", "ctm3"); + assertThat(dbTester.getDbClient().userDismissedMessagesDao().selectByUser(dbTester.getSession(), user)) + .extracting(UserDismissedMessageDto::getUuid) + .containsExactlyInAnyOrder("u1", "u2"); + } + + @Test + @UseDataProvider("allEditions") + public void start_does_nothing_when_no_suggest_upgrade_messages(SonarEdition edition) { + when(sonarRuntime.getEdition()).thenReturn(edition); + + underTest.start(); + + assertThat(dbTester.getDbClient().ceTaskMessageDao().selectByTask(dbTester.getSession(), TASK_UUID)).isEmpty(); + assertThat(dbTester.getDbClient().userDismissedMessagesDao().selectByUser(dbTester.getSession(), user)).isEmpty(); + } + + private void insertCeTaskMessage(String uuid, CeTaskMessageType messageType, String msg) { + CeTaskMessageDto dto = new CeTaskMessageDto() + .setUuid(uuid) + .setMessage(msg) + .setType(messageType) + .setTaskUuid(TASK_UUID); + dbTester.getDbClient().ceTaskMessageDao().insert(dbTester.getSession(), dto); + dbTester.getSession().commit(); + } + + private void insertInUserDismissedMessages(String uuid, CeTaskMessageType messageType) { + UserDismissedMessageDto dto = new UserDismissedMessageDto() + .setUuid(uuid) + .setUserUuid(user.getUuid()) + .setProjectUuid("PROJECT_1") + .setCeMessageType(messageType); + dbTester.getDbClient().userDismissedMessagesDao().insert(dbTester.getSession(), dto); + dbTester.getSession().commit(); + } +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ce/ws/AnalysisStatusAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ce/ws/AnalysisStatusAction.java index 9178c1f7788..55b49a97127 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ce/ws/AnalysisStatusAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ce/ws/AnalysisStatusAction.java @@ -128,7 +128,7 @@ public class AnalysisStatusAction implements CeWsAction { .map(dto -> AnalysisStatusWsResponse.Warning.newBuilder() .setKey(dto.getUuid()) .setMessage(dto.getMessage()) - .setDismissable(dto.isDismissible()) + .setDismissable(dto.getType().isDismissible()) .build()) .collect(Collectors.toList()); diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ce/ws/CeWsModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ce/ws/CeWsModule.java index 10cca51ce45..a08737c40bf 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ce/ws/CeWsModule.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ce/ws/CeWsModule.java @@ -46,6 +46,7 @@ public class CeWsModule extends Module { TaskFormatter.class, TaskAction.class, TaskTypesAction.class, - WorkerCountAction.class); + WorkerCountAction.class, + DismissAnalysisWarningAction.class); } } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ce/ws/DismissAnalysisWarningAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ce/ws/DismissAnalysisWarningAction.java new file mode 100644 index 00000000000..ac9b8270682 --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ce/ws/DismissAnalysisWarningAction.java @@ -0,0 +1,115 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.ce.ws; + +import java.util.Optional; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.web.UserRole; +import org.sonar.core.util.Uuids; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.ce.CeTaskMessageDto; +import org.sonar.db.project.ProjectDto; +import org.sonar.db.user.UserDismissedMessageDto; +import org.sonar.db.user.UserDto; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.user.UserSession; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; +import static org.sonar.server.exceptions.NotFoundException.checkFound; + +public class DismissAnalysisWarningAction implements CeWsAction { + + private static final String MESSAGE_NOT_FOUND = "Message '%s' not found."; + private static final String MESSAGE_CANNOT_BE_DISMISSED = "Message '%s' cannot be dismissed."; + private static final String PARAM_COMPONENT_KEY = "component"; + private static final String PARAM_MESSAGE_KEY = "warning"; + + private final UserSession userSession; + private final DbClient dbClient; + private final ComponentFinder componentFinder; + + public DismissAnalysisWarningAction(UserSession userSession, DbClient dbClient, ComponentFinder componentFinder) { + this.userSession = userSession; + this.dbClient = dbClient; + this.componentFinder = componentFinder; + } + + @Override + public void define(WebService.NewController controller) { + WebService.NewAction action = controller + .createAction("dismiss_analysis_warning") + .setPost(true) + .setDescription("Permanently dismiss a specific analysis warning. Requires authentication and 'Browse' permission on the specified project.") + .setSince("8.5") + .setInternal(true) + .setHandler(this); + + action.createParam(PARAM_COMPONENT_KEY) + .setDescription("Key of the project") + .setRequired(true) + .setExampleValue(Uuids.UUID_EXAMPLE_01); + action.createParam(PARAM_MESSAGE_KEY) + .setDescription("Key of the warning") + .setRequired(true) + .setExampleValue(Uuids.UUID_EXAMPLE_02); + } + + @Override + public void handle(Request request, Response response) throws Exception { + userSession.checkLoggedIn(); + String userLogin = requireNonNull(userSession.getLogin()); + String projectKey = request.mandatoryParam(PARAM_COMPONENT_KEY); + String messageKey = request.mandatoryParam(PARAM_MESSAGE_KEY); + + try (DbSession dbSession = dbClient.openSession(false)) { + UserDto user = getUser(dbSession, userLogin); + ProjectDto project = componentFinder.getProjectByKey(dbSession, projectKey); + userSession.checkProjectPermission(UserRole.USER, project); + + CeTaskMessageDto messageDto = dbClient.ceTaskMessageDao() + .selectByUuid(dbSession, messageKey) + .orElseThrow(() -> new NotFoundException(format(MESSAGE_NOT_FOUND, messageKey))); + if (!messageDto.getType().isDismissible()) { + throw new IllegalArgumentException(format(MESSAGE_CANNOT_BE_DISMISSED, messageKey)); + } + + Optional result = dbClient.userDismissedMessagesDao().selectByUserAndProjectAndMessageType(dbSession, user, project, messageDto.getType()); + if (!result.isPresent()) { + dbClient.userDismissedMessagesDao().insert(dbSession, new UserDismissedMessageDto() + .setUuid(Uuids.create()) + .setUserUuid(user.getUuid()) + .setProjectUuid(project.getUuid()) + .setCeMessageType(messageDto.getType())); + dbSession.commit(); + } + + response.noContent(); + } + } + + private UserDto getUser(DbSession dbSession, String userLogin) { + return checkFound(dbClient.userDao().selectByLogin(dbSession, userLogin), "User '%s' not found", userLogin); + } +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/DeactivateAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/DeactivateAction.java index ac5b47ecbfe..1a201a392e8 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/DeactivateAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/DeactivateAction.java @@ -27,8 +27,6 @@ import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService.NewAction; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; import org.sonar.api.utils.text.JsonWriter; import org.sonar.db.DbClient; import org.sonar.db.DbSession; @@ -49,8 +47,6 @@ import static org.sonar.server.exceptions.NotFoundException.checkFound; public class DeactivateAction implements UsersWsAction { - private static final Logger LOGGER = Loggers.get(DeactivateAction.class); - private static final String PARAM_LOGIN = "login"; private final DbClient dbClient; @@ -109,6 +105,7 @@ public class DeactivateAction implements UsersWsAction { dbClient.userPropertiesDao().deleteByUser(dbSession, user); dbClient.almPatDao().deleteByUser(dbSession, user); dbClient.sessionTokensDao().deleteByUser(dbSession, user); + dbClient.userDismissedMessagesDao().deleteByUser(dbSession, user); dbClient.userDao().deactivateUser(dbSession, user); userIndexer.commitAndIndex(dbSession, user); } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java index 2a1dad02869..4b61f8968f5 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java @@ -40,6 +40,7 @@ import org.sonar.db.ce.CeActivityDto.Status; import org.sonar.db.ce.CeQueueDto; import org.sonar.db.ce.CeTaskCharacteristicDto; import org.sonar.db.ce.CeTaskMessageDto; +import org.sonar.db.ce.CeTaskMessageType; import org.sonar.db.ce.CeTaskTypes; import org.sonar.db.component.BranchType; import org.sonar.db.component.ComponentDto; @@ -247,6 +248,7 @@ public class ActivityActionTest { .setUuid("uuid_" + taskUuid + "_" + i) .setTaskUuid(taskUuid) .setMessage("m_" + taskUuid + "_" + i) + .setType(CeTaskMessageType.GENERIC) .setCreatedAt(taskUuid.hashCode() + i))); db.commit(); } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/AnalysisStatusActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/AnalysisStatusActionTest.java index 906096f8666..62b473c1122 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/AnalysisStatusActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/AnalysisStatusActionTest.java @@ -30,6 +30,7 @@ import org.sonar.db.DbTester; import org.sonar.db.ce.CeActivityDto; import org.sonar.db.ce.CeQueueDto; import org.sonar.db.ce.CeTaskMessageDto; +import org.sonar.db.ce.CeTaskMessageType; import org.sonar.db.component.BranchType; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.SnapshotDto; @@ -92,7 +93,7 @@ public class AnalysisStatusActionTest { SnapshotDto analysis = db.components().insertSnapshot(project); CeActivityDto activity = insertActivity("task-uuid" + counter++, project, SUCCESS, analysis, REPORT); CeTaskMessageDto taskMessage = createTaskMessage(activity, WARNING_IN_MAIN); - CeTaskMessageDto taskMessageDismissible = createTaskMessage(activity, "Dismissible warning", true); + CeTaskMessageDto taskMessageDismissible = createTaskMessage(activity, "Dismissible warning", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); Ce.AnalysisStatusWsResponse response = ws.newRequest() .setParam(PARAM_COMPONENT, project.getKey()) @@ -288,7 +289,7 @@ public class AnalysisStatusActionTest { .setUuid("AU-Tpxb--iU5OvuD2FLy") .setTaskUuid(activity.getUuid()) .setMessage("Property \"sonar.jacoco.reportPaths\" is no longer supported. Use JaCoCo xml report and sonar-jacoco plugin.") - .setDismissible(false) + .setType(CeTaskMessageType.GENERIC) .setCreatedAt(counter); db.getDbClient().ceTaskMessageDao().insert(db.getSession(), ceTaskMessage); db.commit(); @@ -329,15 +330,15 @@ public class AnalysisStatusActionTest { } private CeTaskMessageDto createTaskMessage(CeActivityDto activity, String warning) { - return createTaskMessage(activity, warning, false); + return createTaskMessage(activity, warning, CeTaskMessageType.GENERIC); } - private CeTaskMessageDto createTaskMessage(CeActivityDto activity, String warning, boolean dismissible) { + private CeTaskMessageDto createTaskMessage(CeActivityDto activity, String warning, CeTaskMessageType messageType) { CeTaskMessageDto ceTaskMessageDto = new CeTaskMessageDto() .setUuid("m-uuid-" + counter++) .setTaskUuid(activity.getUuid()) .setMessage(warning) - .setDismissible(dismissible) + .setType(messageType) .setCreatedAt(counter); db.getDbClient().ceTaskMessageDao().insert(db.getSession(), ceTaskMessageDto); db.commit(); diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/CeWsModuleTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/CeWsModuleTest.java index 3ab8d1413bd..0329920b4f2 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/CeWsModuleTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/CeWsModuleTest.java @@ -31,6 +31,6 @@ public class CeWsModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); new CeWsModule().configure(container); - assertThat(container.size()).isEqualTo(19 + COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER); + assertThat(container.size()).isEqualTo(20 + COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER); } } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/ComponentActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/ComponentActionTest.java index 935276da9d1..ea4df38a0b1 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/ComponentActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/ComponentActionTest.java @@ -34,6 +34,7 @@ import org.sonar.db.ce.CeActivityDto; import org.sonar.db.ce.CeQueueDto; import org.sonar.db.ce.CeTaskCharacteristicDto; import org.sonar.db.ce.CeTaskMessageDto; +import org.sonar.db.ce.CeTaskMessageType; import org.sonar.db.ce.CeTaskTypes; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.SnapshotDto; @@ -255,6 +256,7 @@ public class ComponentActionTest { .setUuid("uuid_" + i) .setTaskUuid(activity.getUuid()) .setMessage("m_" + i) + .setType(CeTaskMessageType.GENERIC) .setCreatedAt(i))); db.commit(); diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/DismissAnalysisWarningActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/DismissAnalysisWarningActionTest.java new file mode 100644 index 00000000000..045dd360e8e --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/DismissAnalysisWarningActionTest.java @@ -0,0 +1,208 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.ce.ws; + +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; +import org.sonar.db.DbTester; +import org.sonar.db.ce.CeActivityDto; +import org.sonar.db.ce.CeQueueDto; +import org.sonar.db.ce.CeTaskMessageDto; +import org.sonar.db.ce.CeTaskMessageType; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.SnapshotDto; +import org.sonar.db.project.ProjectDto; +import org.sonar.db.user.UserDto; +import org.sonar.server.component.TestComponentFinder; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.NotFoundException; +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 java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static org.junit.Assert.assertThrows; +import static org.sonar.db.ce.CeActivityDto.Status.SUCCESS; +import static org.sonar.db.ce.CeTaskTypes.REPORT; + +public class DismissAnalysisWarningActionTest { + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + + private static int counter = 1; + + private final WsActionTester underTest = new WsActionTester(new DismissAnalysisWarningAction(userSession, db.getDbClient(), TestComponentFinder.from(db))); + + @Test + public void definition() { + WebService.Action def = underTest.getDef(); + assertThat(def.key()).isEqualTo("dismiss_analysis_warning"); + assertThat(def.isInternal()).isTrue(); + assertThat(def.isPost()).isTrue(); + assertThat(def.params()).extracting(WebService.Param::key, WebService.Param::isRequired).containsOnly( + tuple("component", true), + tuple("warning", true)); + } + + @Test + public void return_401_if_user_is_not_logged_in() { + userSession.anonymous(); + TestRequest request = underTest.newRequest() + .setParam("component", "6653f062-7c03-4b55-bcd2-0dac67640c4d") + .setParam("warning", "55c40b35-4145-4b78-bdf2-dfb242c25f15"); + + assertThrows("Authentication is required", UnauthorizedException.class, request::execute); + } + + @Test + public void return_403_if_user_has_no_browse_permission_on_private_project() { + ProjectDto project = db.components().insertPrivateProjectDto(); + UserDto user = db.users().insertUser(); + userSession.logIn(user); + + TestRequest request = underTest.newRequest() + .setParam("component", project.getKee()) + .setParam("warning", "55c40b35-4145-4b78-bdf2-dfb242c25f15"); + + assertThrows("Insufficient privileges", ForbiddenException.class, request::execute); + } + + @Test + public void return_204_on_success() { + UserDto user = db.users().insertUser(); + ComponentDto project = db.components().insertPrivateProject(); + userSession.logIn(user).addProjectPermission(UserRole.USER, project); + SnapshotDto analysis = db.components().insertSnapshot(project); + CeActivityDto activity = insertActivity("task-uuid" + counter++, project, SUCCESS, analysis, REPORT); + CeTaskMessageDto taskMessageDismissible = createTaskMessage(activity, "dismissable warning", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); + + TestResponse response = underTest.newRequest() + .setParam("component", project.getKey()) + .setParam("warning", taskMessageDismissible.getUuid()) + .execute(); + + assertThat(response.getStatus()).isEqualTo(204); + assertThat(db.select("select * from user_dismissed_messages")) + .extracting("USER_UUID", "PROJECT_UUID", "MESSAGE_TYPE") + .containsExactly(tuple(userSession.getUuid(), project.uuid(), CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE.name())); + } + + @Test + public void is_idempotent() { + UserDto user = db.users().insertUser(); + ComponentDto project = db.components().insertPrivateProject(); + userSession.logIn(user).addProjectPermission(UserRole.USER, project); + SnapshotDto analysis = db.components().insertSnapshot(project); + CeActivityDto activity = insertActivity("task-uuid" + counter++, project, SUCCESS, analysis, REPORT); + CeTaskMessageDto taskMessageDismissible = createTaskMessage(activity, "dismissable warning", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); + + underTest.newRequest() + .setParam("component", project.getKey()) + .setParam("warning", taskMessageDismissible.getUuid()) + .execute(); + TestResponse response = underTest.newRequest() + .setParam("component", project.getKey()) + .setParam("warning", taskMessageDismissible.getUuid()) + .execute(); + + assertThat(response.getStatus()).isEqualTo(204); + assertThat(db.select("select * from user_dismissed_messages")) + .extracting("USER_UUID", "PROJECT_UUID", "MESSAGE_TYPE") + .containsExactly(tuple(userSession.getUuid(), project.uuid(), CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE.name())); + } + + @Test + public void returns_400_if_warning_is_not_dismissable() { + UserDto user = db.users().insertUser(); + ComponentDto project = db.components().insertPrivateProject(); + userSession.logIn(user).addProjectPermission(UserRole.USER, project); + SnapshotDto analysis = db.components().insertSnapshot(project); + CeActivityDto activity = insertActivity("task-uuid" + counter++, project, SUCCESS, analysis, REPORT); + CeTaskMessageDto taskMessage = createTaskMessage(activity, "generic warning"); + + TestRequest request = underTest.newRequest() + .setParam("component", project.getKey()) + .setParam("warning", taskMessage.getUuid()); + + assertThrows(format("Message '%s' cannot be dismissed.", taskMessage.getUuid()), IllegalArgumentException.class, request::execute); + assertThat(db.countRowsOfTable("USER_DISMISSED_MESSAGES")).isZero(); + } + + @Test + public void returns_404_if_warning_does_not_exist() { + UserDto user = db.users().insertUser(); + ComponentDto project = db.components().insertPrivateProject(); + userSession.logIn(user).addProjectPermission(UserRole.USER, project); + SnapshotDto analysis = db.components().insertSnapshot(project); + insertActivity("task-uuid" + counter++, project, SUCCESS, analysis, REPORT); + String warningUuid = "78d1e2ff-3e67-4037-ba58-0d57d5f88e44"; + + TestRequest request = underTest.newRequest() + .setParam("component", project.getKey()) + .setParam("warning", warningUuid); + + assertThrows(format("Message '%s' not found", warningUuid), NotFoundException.class, request::execute); + assertThat(db.countRowsOfTable("USER_DISMISSED_MESSAGES")).isZero(); + } + + private CeTaskMessageDto createTaskMessage(CeActivityDto activity, String warning) { + return createTaskMessage(activity, warning, CeTaskMessageType.GENERIC); + } + + private CeTaskMessageDto createTaskMessage(CeActivityDto activity, String warning, CeTaskMessageType messageType) { + CeTaskMessageDto ceTaskMessageDto = new CeTaskMessageDto() + .setUuid("m-uuid-" + counter++) + .setTaskUuid(activity.getUuid()) + .setMessage(warning) + .setType(messageType) + .setCreatedAt(counter); + db.getDbClient().ceTaskMessageDao().insert(db.getSession(), ceTaskMessageDto); + db.commit(); + return ceTaskMessageDto; + } + + private CeActivityDto insertActivity(String taskUuid, ComponentDto component, CeActivityDto.Status status, @Nullable SnapshotDto analysis, String taskType) { + CeQueueDto queueDto = new CeQueueDto(); + queueDto.setTaskType(taskType); + queueDto.setComponent(component); + queueDto.setUuid(taskUuid); + CeActivityDto activityDto = new CeActivityDto(queueDto); + activityDto.setStatus(status); + activityDto.setExecutionTimeMs(500L); + activityDto.setAnalysisUuid(analysis == null ? null : analysis.getUuid()); + activityDto.setExecutedAt((long) counter++); + activityDto.setTaskType(taskType); + activityDto.setComponentUuid(component.uuid()); + db.getDbClient().ceActivityDao().insert(db.getSession(), activityDto); + db.getSession().commit(); + return activityDto; + } +} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/TaskActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/TaskActionTest.java index 737e218d99a..0e2f2e010eb 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/TaskActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/ce/ws/TaskActionTest.java @@ -38,6 +38,7 @@ import org.sonar.db.ce.CeActivityDto; import org.sonar.db.ce.CeQueueDto; import org.sonar.db.ce.CeTaskCharacteristicDto; import org.sonar.db.ce.CeTaskMessageDto; +import org.sonar.db.ce.CeTaskMessageType; import org.sonar.db.ce.CeTaskTypes; import org.sonar.db.component.ComponentDto; import org.sonar.db.organization.OrganizationDto; @@ -74,9 +75,9 @@ public class TaskActionTest { private OrganizationDto organization; private ComponentDto privateProject; private ComponentDto publicProject; - private TaskFormatter formatter = new TaskFormatter(db.getDbClient(), System2.INSTANCE); - private TaskAction underTest = new TaskAction(db.getDbClient(), formatter, userSession); - private WsActionTester ws = new WsActionTester(underTest); + private final TaskFormatter formatter = new TaskFormatter(db.getDbClient(), System2.INSTANCE); + private final TaskAction underTest = new TaskAction(db.getDbClient(), formatter, userSession); + private final WsActionTester ws = new WsActionTester(underTest); @Before public void setUp() { @@ -125,6 +126,7 @@ public class TaskActionTest { .setUuid("u_" + i) .setTaskUuid(queueDto.getUuid()) .setMessage("m_" + i) + .setType(CeTaskMessageType.GENERIC) .setCreatedAt(queueDto.getUuid().hashCode() + i))); db.commit(); @@ -393,7 +395,7 @@ public class TaskActionTest { CeActivityDto task = createAndPersistArchivedTask(publicProject); expectedException.expect(ForbiddenException.class); - + call(task.getUuid()); } @@ -488,6 +490,7 @@ public class TaskActionTest { .setUuid(UuidFactoryFast.getInstance().create()) .setTaskUuid(task.getUuid()) .setMessage("msg_" + task.getUuid() + "_" + i) + .setType(CeTaskMessageType.GENERIC) .setCreatedAt(task.getUuid().hashCode() + i); db.getDbClient().ceTaskMessageDao().insert(db.getSession(), res); db.getSession().commit(); diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java index caaecbb3ac1..60b84eee1cc 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java @@ -30,15 +30,18 @@ import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.ce.CeTaskMessageType; import org.sonar.db.component.ComponentDto; import org.sonar.db.organization.OrganizationDto; import org.sonar.db.permission.template.PermissionTemplateDto; import org.sonar.db.permission.template.PermissionTemplateUserDto; +import org.sonar.db.project.ProjectDto; import org.sonar.db.property.PropertyDto; import org.sonar.db.property.PropertyQuery; import org.sonar.db.qualityprofile.QProfileDto; import org.sonar.db.user.GroupDto; import org.sonar.db.user.SessionTokenDto; +import org.sonar.db.user.UserDismissedMessageDto; import org.sonar.db.user.UserDto; import org.sonar.server.es.EsTester; import org.sonar.server.exceptions.BadRequestException; @@ -71,7 +74,7 @@ import static org.sonar.test.JsonAssert.assertJson; public class DeactivateActionTest { - private System2 system2 = new AlwaysIncreasingSystem2(); + private final System2 system2 = new AlwaysIncreasingSystem2(); @Rule public ExpectedException expectedException = ExpectedException.none(); @@ -85,11 +88,11 @@ public class DeactivateActionTest { @Rule public UserSessionRule userSession = UserSessionRule.standalone(); - private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db); - private DbClient dbClient = db.getDbClient(); - private UserIndexer userIndexer = new UserIndexer(dbClient, es.client()); - private DbSession dbSession = db.getSession(); - private WsActionTester ws = new WsActionTester(new DeactivateAction(dbClient, userIndexer, userSession, + private final DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db); + private final DbClient dbClient = db.getDbClient(); + private final UserIndexer userIndexer = new UserIndexer(dbClient, es.client()); + private final DbSession dbSession = db.getSession(); + private final WsActionTester ws = new WsActionTester(new DeactivateAction(dbClient, userIndexer, userSession, new UserJsonWriter(userSession), defaultOrganizationProvider)); @Test @@ -265,9 +268,9 @@ public class DeactivateActionTest { logInAsSystemAdministrator(); UserDto user = db.users().insertUser(); SessionTokenDto sessionToken1 = db.users().insertSessionToken(user); - SessionTokenDto sessionToken2 =db.users().insertSessionToken(user); + SessionTokenDto sessionToken2 = db.users().insertSessionToken(user); UserDto anotherUser = db.users().insertUser(); - SessionTokenDto sessionToken3 =db.users().insertSessionToken(anotherUser); + SessionTokenDto sessionToken3 = db.users().insertSessionToken(anotherUser); deactivate(user.getLogin()); @@ -276,6 +279,27 @@ public class DeactivateActionTest { assertThat(db.getDbClient().sessionTokensDao().selectByUuid(dbSession, sessionToken3.getUuid())).isPresent(); } + @Test + public void deactivate_user_deletes_his_dismissed_messages() { + logInAsSystemAdministrator(); + ProjectDto project1 = db.components().insertPrivateProjectDto(); + ProjectDto project2 = db.components().insertPrivateProjectDto(); + UserDto user = db.users().insertUser(); + + db.users().insertUserDismissedMessage(user, project1, CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); + db.users().insertUserDismissedMessage(user, project2, CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); + UserDto anotherUser = db.users().insertUser(); + UserDismissedMessageDto msg3 = db.users().insertUserDismissedMessage(anotherUser, project1, CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); + UserDismissedMessageDto msg4 = db.users().insertUserDismissedMessage(anotherUser, project2, CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); + + deactivate(user.getLogin()); + + assertThat(db.getDbClient().userDismissedMessagesDao().selectByUser(dbSession, user)).isEmpty(); + assertThat(db.getDbClient().userDismissedMessagesDao().selectByUser(dbSession, anotherUser)) + .extracting(UserDismissedMessageDto::getUuid) + .containsExactlyInAnyOrder(msg3.getUuid(), msg4.getUuid()); + } + @Test public void user_cannot_deactivate_itself_on_sonarqube() { UserDto user = db.users().insertUser(); diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java index 6c46862da77..0d17d7971d3 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java @@ -44,6 +44,7 @@ import org.sonar.server.startup.RegisterPlugins; import org.sonar.server.startup.RenameDeprecatedPropertyKeys; import org.sonar.server.user.DoPrivileged; import org.sonar.server.user.ThreadLocalUserSession; +import org.sonar.server.startup.UpgradeSuggestionsCleaner; public class PlatformLevelStartup extends PlatformLevel { public PlatformLevelStartup(PlatformLevel parent) { @@ -70,7 +71,8 @@ public class PlatformLevelStartup extends PlatformLevel { RegisterQualityProfiles.class, RegisterPermissionTemplates.class, RenameDeprecatedPropertyKeys.class, - CeQueueCleaner.class); + CeQueueCleaner.class, + UpgradeSuggestionsCleaner.class); // RegisterServletFilters makes the WebService engine of Level4 served by the MasterServletFilter, therefor it // must be started after all the other startup tasks diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/CeService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/CeService.java index 67637749b54..bec63dbd7e5 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/CeService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/CeService.java @@ -141,6 +141,22 @@ public class CeService extends BaseService { ComponentResponse.parser()); } + /** + * + * This is part of the internal API. + * This is a POST request. + * @see Further information about this action online (including a response example) + * @since 8.5 + */ + public void dismissAnalysisWarning(DismissAnalysisWarningRequest request) { + call( + new PostRequest(path("dismiss_analysis_warning")) + .setParam("component", request.getComponent()) + .setParam("warning", request.getWarning()) + .setMediaType(MediaTypes.JSON) + ).content(); + } + /** * * This is part of the internal API. diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/DismissAnalysisWarningRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/DismissAnalysisWarningRequest.java new file mode 100644 index 00000000000..6c1defaa710 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/DismissAnalysisWarningRequest.java @@ -0,0 +1,59 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.sonarqube.ws.client.ce; + +import javax.annotation.Generated; + +/** + * This is part of the internal API. + * This is a POST request. + * @see Further information about this action online (including a response example) + * @since 8.5 + */ +@Generated("sonar-ws-generator") +public class DismissAnalysisWarningRequest { + + private String component; + private String warning; + + /** + * Example value: "AU-Tpxb--iU5OvuD2FLy" + */ + public DismissAnalysisWarningRequest setComponent(String component) { + this.component = component; + return this; + } + + public String getComponent() { + return component; + } + + /** + * Example value: "AU-TpxcA-iU5OvuD2FLz" + */ + public DismissAnalysisWarningRequest setWarning(String warning) { + this.warning = warning; + return this; + } + + public String getWarning() { + return warning; + } +} -- 2.39.5