@@ -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 <a href=\"https://www.sonarqube.org/trial-request/developer-edition/?referrer=sonarqube-cpp\">upgrading to " + | |||
"the Developer Edition</a> 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 <a target=\"_blank\" href=\"https://www.sonarqube.org/trial-request/developer-edition/?referrer=sonarqube-cpp\">upgrading to" + | |||
" Developer Edition</a> 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<Map.Entry<String, Integer>> iterator = sortedLanguageMap.entrySet().iterator(); | |||
Map.Entry<String, Integer> 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<String, Integer> 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<String, Integer> filesPerLanguage) { |
@@ -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<Message> 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<Message> 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 <a href=\"https://www.sonarqube.org/trial-request/developer-edition/?referrer=sonarqube-cpp\">upgrading to the Developer " + | |||
"Edition</a> 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" + | |||
" <a target=\"_blank\" href=\"https://www.sonarqube.org/trial-request/developer-edition/?referrer=sonarqube-cpp\">upgrading to Developer Edition</a> 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<CeTaskMessages.Message> argumentCaptor = ArgumentCaptor.forClass(CeTaskMessages.Message.class); | |||
underTest.execute(new TestComputationStepContext()); | |||
verify(ceTaskMessages, times(1)).add(argumentCaptor.capture()); | |||
List<CeTaskMessages.Message> 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 <a href=\"https://www.sonarqube.org/trial-request/developer-edition/?referrer=sonarqube-cpp\">upgrading to the Developer " + | |||
"Edition</a> 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 <a target=\"_blank\" href=\"https://www.sonarqube.org/trial-request/developer-edition/?referrer=sonarqube-cpp\">upgrading to Developer" + | |||
" Edition</a> 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<CeTaskMessages.Message> argumentCaptor = ArgumentCaptor.forClass(CeTaskMessages.Message.class); | |||
underTest.execute(new TestComputationStepContext()); | |||
verify(ceTaskMessages, times(1)).add(argumentCaptor.capture()); | |||
List<CeTaskMessages.Message> 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 <a href=\"https://www.sonarqube.org/trial-request/developer-edition/?referrer=sonarqube-cpp\">upgrading to the Developer " + | |||
"Edition</a> 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 <a target=\"_blank\" href=\"https://www.sonarqube.org/trial-request/developer-edition/?referrer=sonarqube-cpp\">upgrading to Developer" + | |||
" Edition</a> 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(); | |||
} | |||
@@ -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<List<CeTaskMessages.Message>> 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<ScannerReport.AnalysisWarning> warnings = of(warning1, warning2); | |||
reportReader.setAnalysisWarnings(warnings); | |||
ArgumentCaptor<List<CeTaskMessages.Message>> 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 |
@@ -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 + | |||
'}'; | |||
} | |||
} |
@@ -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) { |
@@ -115,6 +115,7 @@ public final class SqTables { | |||
"session_tokens", | |||
"snapshots", | |||
"users", | |||
"user_dismissed_messages", | |||
"user_properties", | |||
"user_roles", | |||
"user_tokens", |
@@ -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, |
@@ -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; | |||
} | |||
} |
@@ -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, |
@@ -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<CeTaskMessageDto> 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); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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<CeTaskMessageDto> selectByUuid(@Param("uuid") String uuid); | |||
List<CeTaskMessageDto> selectByTask(@Param("taskUuid") String taskUuid); | |||
void deleteByType(@Param("ceMessageType") String ceMessageType); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -224,6 +224,7 @@ public class PurgeDao implements Dao { | |||
commands.deleteComponents(rootUuid); | |||
commands.deleteComponentsByMainBranchProjectUuid(rootUuid); | |||
commands.deleteProject(rootUuid); | |||
commands.deleteUserDismissedMessages(rootUuid); | |||
} | |||
/** |
@@ -150,4 +150,6 @@ public interface PurgeMapper { | |||
void deleteNewCodePeriodsByRootUuid(String rootUuid); | |||
void deleteProjectAlmSettingsByProjectUuid(@Param("projectUuid") String projectUuid); | |||
void deleteUserDismissedMessagesByProjectUuid(@Param("projectUuid") String projectUuid); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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<UserDismissedMessageDto> selectByUserAndProjectAndMessageType(DbSession session, UserDto user, ProjectDto project, | |||
CeTaskMessageType ceMessageType) { | |||
return mapper(session).selectByUserUuidAndProjectUuidAndMessageType(user.getUuid(), project.getUuid(), ceMessageType.name()); | |||
} | |||
public List<UserDismissedMessageDto> 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); | |||
} | |||
} |
@@ -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<UserDismissedMessageDto> selectByUserUuidAndProjectUuidAndMessageType(@Param("userUuid") String userUuid, @Param("projectUuid") String projectUuid, | |||
@Param("ceMessageType") String ceMessageType); | |||
List<UserDismissedMessageDto> selectByUserUuid(@Param("userUuid") String userUuid); | |||
void deleteByUserUuid(@Param("userUuid") String userUuid); | |||
void deleteByType(@Param("ceMessageType") String ceMessageType); | |||
} |
@@ -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 | |||
</sql> | |||
<select id="selectByUuid" resultType="org.sonar.db.ce.CeTaskMessageDto"> | |||
select | |||
<include refid="columns"/> | |||
from | |||
ce_task_message ctm | |||
where | |||
ctm.uuid=#{uuid,jdbcType=VARCHAR} | |||
</select> | |||
<select id="selectByTask" resultType="org.sonar.db.ce.CeTaskMessageDto"> | |||
select | |||
<include refid="columns"/> | |||
@@ -28,16 +37,20 @@ | |||
uuid, | |||
task_uuid, | |||
message, | |||
created_at, | |||
is_dismissible | |||
message_type, | |||
created_at | |||
) | |||
values ( | |||
#{dto.uuid,jdbcType=VARCHAR}, | |||
#{dto.taskUuid,jdbcType=VARCHAR}, | |||
#{dto.message,jdbcType=VARCHAR}, | |||
#{dto.createdAt,jdbcType=BIGINT}, | |||
#{dto.dismissible,jdbcType=BOOLEAN} | |||
#{dto.type,jdbcType=VARCHAR}, | |||
#{dto.createdAt,jdbcType=BIGINT} | |||
) | |||
</insert> | |||
<delete id="deleteByType" parameterType="String"> | |||
delete from ce_task_message where message_type = #{ceMessageType, jdbcType=VARCHAR} | |||
</delete> | |||
</mapper> |
@@ -504,5 +504,9 @@ | |||
<delete id="deleteLiveMeasuresByComponentUuids"> | |||
delete from live_measures where component_uuid in <foreach item="componentUuid" index="index" collection="componentUuids" open="(" separator="," close=")">#{componentUuid, jdbcType=VARCHAR}</foreach> | |||
</delete> | |||
<delete id="deleteUserDismissedMessagesByProjectUuid"> | |||
delete from user_dismissed_messages where project_uuid = #{projectUuid,jdbcType=VARCHAR} | |||
</delete> | |||
</mapper> | |||
@@ -0,0 +1,60 @@ | |||
<?xml version="1.0" encoding="UTF-8" ?> | |||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd"> | |||
<mapper namespace="org.sonar.db.user.UserDismissedMessagesMapper"> | |||
<sql id="columns"> | |||
udm.uuid, | |||
udm.user_uuid as userUuid, | |||
udm.project_uuid as projectUuid, | |||
udm.message_type as ceMessageType, | |||
udm.created_at as createdAt | |||
</sql> | |||
<insert id="insert" parameterType="org.sonar.db.user.UserDismissedMessageDto" useGeneratedKeys="false"> | |||
insert into user_dismissed_messages | |||
( | |||
uuid, | |||
user_uuid, | |||
project_uuid, | |||
message_type, | |||
created_at | |||
) | |||
values ( | |||
#{dto.uuid,jdbcType=VARCHAR}, | |||
#{dto.userUuid,jdbcType=VARCHAR}, | |||
#{dto.projectUuid,jdbcType=VARCHAR}, | |||
#{dto.ceMessageType,jdbcType=VARCHAR}, | |||
#{dto.createdAt,jdbcType=BIGINT} | |||
) | |||
</insert> | |||
<select id="selectByUserUuidAndProjectUuidAndMessageType" resultType="org.sonar.db.user.UserDismissedMessageDto"> | |||
select | |||
<include refid="columns"/> | |||
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} | |||
</select> | |||
<select id="selectByUserUuid" resultType="org.sonar.db.user.UserDismissedMessageDto" parameterType="String"> | |||
select | |||
<include refid="columns"/> | |||
from | |||
user_dismissed_messages udm | |||
where | |||
udm.user_uuid=#{userUuid,jdbcType=VARCHAR} | |||
</select> | |||
<delete id="deleteByUserUuid" parameterType="String"> | |||
delete from user_dismissed_messages where user_uuid = #{userUuid, jdbcType=VARCHAR} | |||
</delete> | |||
<delete id="deleteByType" parameterType="String"> | |||
delete from user_dismissed_messages where message_type = #{ceMessageType, jdbcType=VARCHAR} | |||
</delete> | |||
</mapper> |
@@ -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, |
@@ -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(); | |||
} |
@@ -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<CeTaskMessageDto> 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<CeTaskMessageDto> 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); |
@@ -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); |
@@ -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(); |
@@ -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<UserDismissedMessageDto> 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<UserDismissedMessageDto> 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<UserDismissedMessageDto> 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<UserDismissedMessageDto> 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()); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -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 | |||
); | |||
} | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -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; | |||
}); |
@@ -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"); | |||
} | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -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"); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -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"); |
@@ -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"); |
@@ -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"); |
@@ -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"); |
@@ -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 | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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()); | |||
@@ -46,6 +46,7 @@ public class CeWsModule extends Module { | |||
TaskFormatter.class, | |||
TaskAction.class, | |||
TaskTypesAction.class, | |||
WorkerCountAction.class); | |||
WorkerCountAction.class, | |||
DismissAnalysisWarningAction.class); | |||
} | |||
} |
@@ -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<UserDismissedMessageDto> 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); | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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(); | |||
} |
@@ -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(); |
@@ -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); | |||
} | |||
} |
@@ -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(); | |||
@@ -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; | |||
} | |||
} |
@@ -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(); |
@@ -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(); |
@@ -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 |
@@ -141,6 +141,22 @@ public class CeService extends BaseService { | |||
ComponentResponse.parser()); | |||
} | |||
/** | |||
* | |||
* This is part of the internal API. | |||
* This is a POST request. | |||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/ce/dismiss_analysis_warning">Further information about this action online (including a response example)</a> | |||
* @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. |
@@ -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 <a href="https://next.sonarqube.com/sonarqube/web_api/api/ce/dismiss_analysis_warning">Further information about this action online (including a response example)</a> | |||
* @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; | |||
} | |||
} |