@@ -101,7 +101,7 @@ public class PerformNotAnalyzedFilesCheckStep implements ComputationStep { | |||
fileCountLabel.append(format("%s %s", nextLanguage.getValue(), nextLanguage.getKey())); | |||
} | |||
return new CeTaskMessages.Message(format(LANGUAGE_UPGRADE_MESSAGE, fileCountLabel, languageLabel), system.now()); | |||
return new CeTaskMessages.Message(format(LANGUAGE_UPGRADE_MESSAGE, fileCountLabel, languageLabel), system.now(), true); | |||
} | |||
@Override |
@@ -27,6 +27,7 @@ import org.junit.Test; | |||
import org.mockito.ArgumentCaptor; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.ce.task.log.CeTaskMessages; | |||
import org.sonar.ce.task.log.CeTaskMessages.Message; | |||
import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule; | |||
import org.sonar.ce.task.step.TestComputationStepContext; | |||
import org.sonar.core.platform.EditionProvider; | |||
@@ -35,6 +36,7 @@ import org.sonar.scanner.protocol.output.ScannerReport; | |||
import static com.google.common.collect.ImmutableList.of; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.tuple; | |||
import static org.mockito.ArgumentMatchers.any; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.never; | |||
@@ -68,16 +70,18 @@ public class PerformNotAnalyzedFilesCheckStepTest { | |||
.putNotAnalyzedFilesByLanguage("C", 10) | |||
.putNotAnalyzedFilesByLanguage("SomeLang", 1000) | |||
.build()); | |||
ArgumentCaptor<CeTaskMessages.Message> argumentCaptor = ArgumentCaptor.forClass(CeTaskMessages.Message.class); | |||
ArgumentCaptor<Message> argumentCaptor = ArgumentCaptor.forClass(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, 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."); | |||
assertThat(argumentCaptor.getAllValues()) | |||
.extracting(Message::getText, Message::isDismissible) | |||
.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)); | |||
} | |||
@Test |
@@ -21,9 +21,9 @@ package org.sonar.ce.task.projectanalysis.step; | |||
import com.google.common.collect.ImmutableList; | |||
import java.util.List; | |||
import java.util.stream.Collectors; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.mockito.ArgumentCaptor; | |||
import org.sonar.ce.task.log.CeTaskMessages; | |||
import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule; | |||
import org.sonar.ce.task.step.TestComputationStepContext; | |||
@@ -32,7 +32,9 @@ import org.sonar.scanner.protocol.output.ScannerReport; | |||
import static com.google.common.collect.ImmutableList.of; | |||
import static java.util.Collections.emptyList; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.tuple; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.times; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.verifyNoInteractions; | |||
@@ -55,13 +57,14 @@ 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()); | |||
List<CeTaskMessages.Message> messages = warnings.stream() | |||
.map(w -> new CeTaskMessages.Message(w.getText(), w.getTimestamp())) | |||
.collect(Collectors.toList()); | |||
verify(ceTaskMessages).addAll(messages); | |||
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)); | |||
} | |||
@Test |
@@ -25,6 +25,7 @@ import javax.annotation.concurrent.Immutable; | |||
import org.sonar.api.ce.ComputeEngineSide; | |||
import static com.google.common.base.Preconditions.checkArgument; | |||
import static java.util.Objects.requireNonNull; | |||
/** | |||
* Provides the ability to record message attached to the current task. | |||
@@ -47,12 +48,19 @@ public interface CeTaskMessages { | |||
class Message { | |||
private final String text; | |||
private final long timestamp; | |||
private final boolean dismissible; | |||
public Message(String text, long timestamp) { | |||
checkArgument(text != null && !text.isEmpty(), "Text can't be null nor empty"); | |||
checkArgument(timestamp >= 0, "Text can't be less than 0"); | |||
public Message(String text, long timestamp, boolean dismissible) { | |||
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; | |||
} | |||
public Message(String text, long timestamp) { | |||
this(text, timestamp, false); | |||
} | |||
public String getText() { | |||
@@ -63,6 +71,10 @@ public interface CeTaskMessages { | |||
return timestamp; | |||
} | |||
public boolean isDismissible() { | |||
return dismissible; | |||
} | |||
@Override | |||
public boolean equals(Object o) { | |||
if (this == o) { | |||
@@ -86,6 +98,7 @@ public interface CeTaskMessages { | |||
return "Message{" + | |||
"text='" + text + '\'' + | |||
", timestamp=" + timestamp + | |||
", dismissible=" + dismissible + | |||
'}'; | |||
} | |||
} |
@@ -74,6 +74,7 @@ public class CeTaskMessagesImpl implements CeTaskMessages { | |||
.setUuid(uuidFactory.create()) | |||
.setTaskUuid(ceTask.getUuid()) | |||
.setMessage(message.getText()) | |||
.setDismissible(message.isDismissible()) | |||
.setCreatedAt(message.getTimestamp())); | |||
} | |||
@@ -19,76 +19,62 @@ | |||
*/ | |||
package org.sonar.ce.task.log; | |||
import java.util.Random; | |||
import java.util.stream.LongStream; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.ce.task.log.CeTaskMessages.Message; | |||
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | |||
public class CeTaskMessagesMessageTest { | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
@Test | |||
public void constructor_throws_IAE_if_text_is_null() { | |||
expectTextCantBeNullNorEmptyIAE(); | |||
new Message(null, 12L); | |||
assertThatThrownBy(() -> new Message(null, 12L)) | |||
.isInstanceOf(NullPointerException.class) | |||
.hasMessage("Text can't be null"); | |||
} | |||
@Test | |||
public void constructor_throws_IAE_if_text_is_empty() { | |||
expectTextCantBeNullNorEmptyIAE(); | |||
new Message("", 12L); | |||
} | |||
private void expectTextCantBeNullNorEmptyIAE() { | |||
expectedException.expect(IllegalArgumentException.class); | |||
expectedException.expectMessage("Text can't be null nor empty"); | |||
assertThatThrownBy(() -> new Message("", 12L)) | |||
.isInstanceOf(IllegalArgumentException.class) | |||
.hasMessage("Text can't be empty"); | |||
} | |||
@Test | |||
public void constructor_throws_IAE_if_timestamp_is_less_than_0() { | |||
LongStream.of(0, 1 + new Random().nextInt(12)) | |||
.forEach(timestamp -> assertThat(new Message("foo", timestamp).getTimestamp()).isEqualTo(timestamp)); | |||
long lessThanZero = -1 - new Random().nextInt(33); | |||
expectedException.expect(IllegalArgumentException.class); | |||
expectedException.expectMessage("Text can't be less than 0"); | |||
new Message("bar", lessThanZero); | |||
assertThatThrownBy(() -> new Message("bar", -1)) | |||
.isInstanceOf(IllegalArgumentException.class) | |||
.hasMessage("Timestamp can't be less than 0"); | |||
} | |||
@Test | |||
public void equals_is_based_on_text_and_timestamp() { | |||
long timestamp = new Random().nextInt(10_999); | |||
long timestamp = 10_000_000_000L; | |||
String text = randomAlphabetic(23); | |||
Message underTest = new Message(text, timestamp); | |||
assertThat(underTest).isEqualTo(underTest); | |||
assertThat(underTest).isEqualTo(new Message(text, timestamp)); | |||
assertThat(underTest).isNotEqualTo(new Message(text + "ç", timestamp)); | |||
assertThat(underTest).isNotEqualTo(new Message(text, timestamp + 10_999L)); | |||
assertThat(underTest).isNotEqualTo(null); | |||
assertThat(underTest).isNotEqualTo(new Object()); | |||
assertThat(underTest) | |||
.isEqualTo(underTest) | |||
.isEqualTo(new Message(text, timestamp)) | |||
.isNotEqualTo(new Message(text + "ç", timestamp)) | |||
.isNotEqualTo(new Message(text, timestamp + 10_999L)) | |||
.isNotEqualTo(null) | |||
.isNotEqualTo(new Object()); | |||
} | |||
@Test | |||
public void hashsode_is_based_on_text_and_timestamp() { | |||
long timestamp = new Random().nextInt(10_999); | |||
long timestamp = 10_000_000_000L; | |||
String text = randomAlphabetic(23); | |||
Message underTest = new Message(text, timestamp); | |||
assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode()); | |||
assertThat(underTest.hashCode()).isEqualTo(new Message(text, timestamp).hashCode()); | |||
assertThat(underTest.hashCode()).isNotEqualTo(new Message(text + "ç", timestamp).hashCode()); | |||
assertThat(underTest.hashCode()).isNotEqualTo(new Message(text, timestamp + 10_999L).hashCode()); | |||
assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode()); | |||
assertThat(underTest.hashCode()) | |||
.isEqualTo(underTest.hashCode()) | |||
.isEqualTo(new Message(text, timestamp).hashCode()) | |||
.isNotEqualTo(new Message(text + "ç", timestamp).hashCode()) | |||
.isNotEqualTo(new Message(text, timestamp + 10_999L).hashCode()) | |||
.isNotEqualTo(new Object().hashCode()); | |||
} | |||
} |
@@ -38,6 +38,10 @@ public class CeTaskMessageDto { | |||
* Timestamp the message was created. Not null | |||
*/ | |||
private long createdAt; | |||
/** | |||
* Information if this message can be dismissed by the user | |||
*/ | |||
private boolean dismissible; | |||
public String getUuid() { | |||
return uuid; | |||
@@ -76,4 +80,13 @@ public class CeTaskMessageDto { | |||
this.createdAt = createdAt; | |||
return this; | |||
} | |||
public boolean isDismissible() { | |||
return dismissible; | |||
} | |||
public CeTaskMessageDto setDismissible(boolean dismissible) { | |||
this.dismissible = dismissible; | |||
return this; | |||
} | |||
} |
@@ -7,7 +7,8 @@ | |||
ctm.uuid, | |||
ctm.task_uuid as taskUuid, | |||
ctm.message as message, | |||
ctm.created_at as createdAt | |||
ctm.created_at as createdAt, | |||
ctm.is_dismissible as dismissible | |||
</sql> | |||
<select id="selectByTask" resultType="org.sonar.db.ce.CeTaskMessageDto"> | |||
@@ -27,13 +28,15 @@ | |||
uuid, | |||
task_uuid, | |||
message, | |||
created_at | |||
created_at, | |||
is_dismissible | |||
) | |||
values ( | |||
#{dto.uuid,jdbcType=VARCHAR}, | |||
#{dto.taskUuid,jdbcType=VARCHAR}, | |||
#{dto.message,jdbcType=VARCHAR}, | |||
#{dto.createdAt,jdbcType=BIGINT} | |||
#{dto.createdAt,jdbcType=BIGINT}, | |||
#{dto.dismissible,jdbcType=BOOLEAN} | |||
) | |||
</insert> | |||
@@ -168,7 +168,8 @@ 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 | |||
"CREATED_AT" BIGINT NOT NULL, | |||
"IS_DISMISSIBLE" BOOLEAN 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,49 @@ | |||
/* | |||
* 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.BooleanColumnDef; | |||
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; | |||
public class AddIsDismissibleColumnToCeTaskMessageTable 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) | |||
.setIsNullable(true) | |||
.build(); | |||
public AddIsDismissibleColumnToCeTaskMessageTable(Database db) { | |||
super(db); | |||
} | |||
@Override | |||
public void execute(Context context) throws SQLException { | |||
context.execute(new AddColumnsBuilder(getDialect(), TABLE) | |||
.addColumn(IS_DISMISSIBLE) | |||
.build()); | |||
} | |||
} |
@@ -47,7 +47,9 @@ 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) | |||
; | |||
} | |||
} |
@@ -0,0 +1,50 @@ | |||
/* | |||
* 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.BooleanColumnDef; | |||
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; | |||
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) | |||
.setIsNullable(false) | |||
.build(); | |||
public MakeIsDismissibleColumnNotNullableOnCeTaskMessageTable(Database db) { | |||
super(db); | |||
} | |||
@Override | |||
public void execute(Context context) throws SQLException { | |||
context.execute(new AlterColumnsBuilder(getDialect(), TABLE) | |||
.updateColumn(IS_DISMISSIBLE) | |||
.build()); | |||
} | |||
} |
@@ -0,0 +1,45 @@ | |||
/* | |||
* 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.step.DataChange; | |||
import org.sonar.server.platform.db.migration.step.MassUpdate; | |||
public class PopulateIsDismissibleColumnOfCeTaskMessageTable extends DataChange { | |||
public PopulateIsDismissibleColumnOfCeTaskMessageTable(Database db) { | |||
super(db); | |||
} | |||
@Override | |||
protected void execute(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.execute((row, update) -> { | |||
update.setBoolean(1, false); | |||
update.setString(2, row.getString(1)); | |||
return true; | |||
}); | |||
} | |||
} |
@@ -0,0 +1,45 @@ | |||
/* | |||
* 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 org.sonar.server.platform.db.migration.step.DdlChange; | |||
import static java.sql.Types.BOOLEAN; | |||
public class AddIsDismissibleColumnToCeTaskMessageTableTest { | |||
@Rule | |||
public CoreDbTester db = CoreDbTester.createForSchema(AddIsDismissibleColumnToCeTaskMessageTableTest.class, "schema.sql"); | |||
DdlChange underTest = new AddIsDismissibleColumnToCeTaskMessageTable(db.database()); | |||
@Test | |||
public void add_column() throws SQLException { | |||
underTest.execute(); | |||
db.assertColumnDefinition("ce_task_message", "is_dismissible", BOOLEAN, null, true); | |||
} | |||
} |
@@ -0,0 +1,44 @@ | |||
/* | |||
* 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 org.sonar.server.platform.db.migration.step.MigrationStep; | |||
import static java.sql.Types.BOOLEAN; | |||
public class MakeIsDismissibleColumnNotNullableOnCeTaskMessageTableTest { | |||
@Rule | |||
public CoreDbTester db = CoreDbTester.createForSchema(MakeIsDismissibleColumnNotNullableOnCeTaskMessageTableTest.class, "schema.sql"); | |||
private MigrationStep underTest = new MakeIsDismissibleColumnNotNullableOnCeTaskMessageTable(db.database()); | |||
@Test | |||
public void ce_task_message_column_is_not_null() throws SQLException { | |||
underTest.execute(); | |||
db.assertColumnDefinition("ce_task_message", "is_dismissible", BOOLEAN, null, false); | |||
} | |||
} |
@@ -0,0 +1,87 @@ | |||
/* | |||
* 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,8 @@ | |||
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 | |||
); | |||
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, | |||
"IS_DISMISSIBLE" BOOLEAN 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, | |||
"IS_DISMISSIBLE" BOOLEAN 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"); |
@@ -29,7 +29,6 @@ import org.sonar.api.web.UserRole; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.ce.CeActivityDto; | |||
import org.sonar.db.ce.CeTaskMessageDto; | |||
import org.sonar.db.ce.CeTaskTypes; | |||
import org.sonar.db.component.BranchDto; | |||
import org.sonar.db.project.ProjectDto; | |||
@@ -122,13 +121,18 @@ public class AnalysisStatusAction implements CeWsAction { | |||
builder.setPullRequest(pullRequestKey); | |||
} | |||
if (lastActivity != null) { | |||
List<String> warnings = dbClient.ceTaskMessageDao().selectByTask(dbSession, lastActivity.getUuid()).stream() | |||
.map(CeTaskMessageDto::getMessage) | |||
.collect(Collectors.toList()); | |||
builder.addAllWarnings(warnings); | |||
if (lastActivity == null) { | |||
return builder.build(); | |||
} | |||
List<AnalysisStatusWsResponse.Warning> warnings = dbClient.ceTaskMessageDao().selectByTask(dbSession, lastActivity.getUuid()).stream() | |||
.map(dto -> AnalysisStatusWsResponse.Warning.newBuilder() | |||
.setKey(dto.getUuid()) | |||
.setMessage(dto.getMessage()) | |||
.setDismissable(dto.isDismissible()) | |||
.build()) | |||
.collect(Collectors.toList()); | |||
builder.addAllWarnings(warnings); | |||
return builder.build(); | |||
} |
@@ -3,6 +3,12 @@ | |||
"organization": "my-org-1", | |||
"key": "com.github.kevinsawicki:http-request-parent", | |||
"name": "HttpRequest", | |||
"warnings": [] | |||
"warnings": [ | |||
{ | |||
"key": "AU-Tpxb--iU5OvuD2FLy", | |||
"message": "Property \"sonar.jacoco.reportPaths\" is no longer supported. Use JaCoCo xml report and sonar-jacoco plugin.", | |||
"dismissable": false | |||
} | |||
] | |||
} | |||
} |
@@ -40,8 +40,10 @@ import org.sonar.server.exceptions.NotFoundException; | |||
import org.sonar.server.tester.UserSessionRule; | |||
import org.sonar.server.ws.WsActionTester; | |||
import org.sonarqube.ws.Ce; | |||
import org.sonarqube.ws.Ce.AnalysisStatusWsResponse.Warning; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.tuple; | |||
import static org.sonar.db.ce.CeActivityDto.Status.SUCCESS; | |||
import static org.sonar.db.ce.CeTaskTypes.REPORT; | |||
import static org.sonar.server.ce.ws.CeWsParameters.PARAM_BRANCH; | |||
@@ -67,53 +69,8 @@ public class AnalysisStatusActionTest { | |||
@Rule | |||
public DbTester db = DbTester.create(System2.INSTANCE); | |||
private DbClient dbClient = db.getDbClient(); | |||
private WsActionTester ws = new WsActionTester(new AnalysisStatusAction(userSession, dbClient, TestComponentFinder.from(db))); | |||
@Test | |||
public void fail_if_component_key_not_provided() { | |||
expectedException.expect(IllegalArgumentException.class); | |||
ws.newRequest().execute(); | |||
} | |||
@Test | |||
public void fail_if_component_key_is_unknown() { | |||
expectedException.expect(NotFoundException.class); | |||
ws.newRequest().setParam(PARAM_COMPONENT, "nonexistent").execute(); | |||
} | |||
@Test | |||
public void fail_if_both_branch_and_pullRequest_are_specified() { | |||
expectedException.expect(BadRequestException.class); | |||
ws.newRequest() | |||
.setParam(PARAM_COMPONENT, "dummy") | |||
.setParam(PARAM_BRANCH, "feature1") | |||
.setParam(PARAM_PULL_REQUEST, "pr1") | |||
.execute(); | |||
} | |||
@Test | |||
public void json_example() { | |||
OrganizationDto organization = db.organizations().insert(o -> o.setKey("my-org-1")); | |||
ComponentDto project = db.components().insertPrivateProject(organization, | |||
p -> p.setUuid("AU_w74XMgAS1Hm6h4-Y-") | |||
.setProjectUuid("AU_w74XMgAS1Hm6h4-Y-") | |||
.setRootUuid("AU_w74XMgAS1Hm6h4-Y-") | |||
.setDbKey("com.github.kevinsawicki:http-request-parent") | |||
.setName("HttpRequest")); | |||
userSession.addProjectPermission(UserRole.USER, project); | |||
String result = ws.newRequest() | |||
.setParam(PARAM_COMPONENT, project.getKey()) | |||
.execute() | |||
.getInput(); | |||
assertJson(result).isSimilarTo(getClass().getResource("analysis_status-example.json")); | |||
} | |||
DbClient dbClient = db.getDbClient(); | |||
WsActionTester ws = new WsActionTester(new AnalysisStatusAction(userSession, dbClient, TestComponentFinder.from(db))); | |||
@Test | |||
public void no_errors_no_warnings() { | |||
@@ -134,13 +91,18 @@ public class AnalysisStatusActionTest { | |||
SnapshotDto analysis = db.components().insertSnapshot(project); | |||
CeActivityDto activity = insertActivity("task-uuid" + counter++, project, SUCCESS, analysis, REPORT); | |||
createTaskMessage(activity, WARNING_IN_MAIN); | |||
CeTaskMessageDto taskMessage = createTaskMessage(activity, WARNING_IN_MAIN); | |||
CeTaskMessageDto taskMessageDismissible = createTaskMessage(activity, "Dismissible warning", true); | |||
Ce.AnalysisStatusWsResponse response = ws.newRequest() | |||
.setParam(PARAM_COMPONENT, project.getKey()) | |||
.executeProtobuf(Ce.AnalysisStatusWsResponse.class); | |||
assertThat(response.getComponent().getWarningsList()).containsExactly(WARNING_IN_MAIN); | |||
assertThat(response.getComponent().getWarningsList()) | |||
.extracting(Warning::getKey, Warning::getMessage, Warning::getDismissable) | |||
.containsExactly( | |||
tuple(taskMessage.getUuid(), WARNING_IN_MAIN, false), | |||
tuple(taskMessageDismissible.getUuid(), taskMessageDismissible.getMessage(), true)); | |||
SnapshotDto analysis2 = db.components().insertSnapshot(project); | |||
insertActivity("task-uuid" + counter++, project, SUCCESS, analysis2, REPORT); | |||
@@ -161,14 +123,16 @@ public class AnalysisStatusActionTest { | |||
ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey(BRANCH_WITH_WARNING)); | |||
SnapshotDto analysis = db.components().insertSnapshot(branch); | |||
CeActivityDto activity = insertActivity("task-uuid" + counter++, branch, SUCCESS, analysis, REPORT); | |||
createTaskMessage(activity, WARNING_IN_BRANCH); | |||
CeTaskMessageDto taskMessage = createTaskMessage(activity, WARNING_IN_BRANCH); | |||
Ce.AnalysisStatusWsResponse response = ws.newRequest() | |||
.setParam(PARAM_COMPONENT, project.getKey()) | |||
.setParam(PARAM_BRANCH, BRANCH_WITH_WARNING) | |||
.executeProtobuf(Ce.AnalysisStatusWsResponse.class); | |||
assertThat(response.getComponent().getWarningsList()).containsExactly(WARNING_IN_BRANCH); | |||
assertThat(response.getComponent().getWarningsList()) | |||
.extracting(Warning::getKey, Warning::getMessage, Warning::getDismissable) | |||
.containsExactly(tuple(taskMessage.getUuid(), WARNING_IN_BRANCH, false)); | |||
SnapshotDto analysis2 = db.components().insertSnapshot(branch); | |||
insertActivity("task-uuid" + counter++, branch, SUCCESS, analysis2, REPORT); | |||
@@ -193,14 +157,16 @@ public class AnalysisStatusActionTest { | |||
}); | |||
SnapshotDto analysis = db.components().insertSnapshot(pullRequest); | |||
CeActivityDto activity = insertActivity("task-uuid" + counter++, pullRequest, SUCCESS, analysis, REPORT); | |||
createTaskMessage(activity, WARNING_IN_PR); | |||
CeTaskMessageDto taskMessage = createTaskMessage(activity, WARNING_IN_PR); | |||
Ce.AnalysisStatusWsResponse response = ws.newRequest() | |||
.setParam(PARAM_COMPONENT, project.getKey()) | |||
.setParam(PARAM_PULL_REQUEST, PULL_REQUEST) | |||
.executeProtobuf(Ce.AnalysisStatusWsResponse.class); | |||
assertThat(response.getComponent().getWarningsList()).containsExactly(WARNING_IN_PR); | |||
assertThat(response.getComponent().getWarningsList()) | |||
.extracting(Warning::getKey, Warning::getMessage, Warning::getDismissable) | |||
.containsExactly(tuple(taskMessage.getUuid(), WARNING_IN_PR, false)); | |||
SnapshotDto analysis2 = db.components().insertSnapshot(pullRequest); | |||
insertActivity("task-uuid" + counter++, pullRequest, SUCCESS, analysis2, REPORT); | |||
@@ -221,12 +187,12 @@ public class AnalysisStatusActionTest { | |||
SnapshotDto analysis = db.components().insertSnapshot(project); | |||
CeActivityDto activity = insertActivity("task-uuid" + counter++, project, SUCCESS, analysis, REPORT); | |||
createTaskMessage(activity, WARNING_IN_MAIN); | |||
CeTaskMessageDto warningInMainMessage = createTaskMessage(activity, WARNING_IN_MAIN); | |||
ComponentDto branchWithWarning = db.components().insertProjectBranch(project, b -> b.setKey(BRANCH_WITH_WARNING)); | |||
SnapshotDto branchAnalysis = db.components().insertSnapshot(branchWithWarning); | |||
CeActivityDto branchActivity = insertActivity("task-uuid" + counter++, branchWithWarning, SUCCESS, branchAnalysis, REPORT); | |||
createTaskMessage(branchActivity, WARNING_IN_BRANCH); | |||
CeTaskMessageDto warningInBranchMessage = createTaskMessage(branchActivity, WARNING_IN_BRANCH); | |||
ComponentDto branchWithoutWarning = db.components().insertProjectBranch(project, b -> b.setKey(BRANCH_WITHOUT_WARNING)); | |||
SnapshotDto branchWithoutWarningAnalysis = db.components().insertSnapshot(branchWithoutWarning); | |||
@@ -238,20 +204,24 @@ public class AnalysisStatusActionTest { | |||
}); | |||
SnapshotDto prAnalysis = db.components().insertSnapshot(pullRequest); | |||
CeActivityDto prActivity = insertActivity("task-uuid" + counter++, pullRequest, SUCCESS, prAnalysis, REPORT); | |||
createTaskMessage(prActivity, WARNING_IN_PR); | |||
CeTaskMessageDto warningInPrMessage = createTaskMessage(prActivity, WARNING_IN_PR); | |||
Ce.AnalysisStatusWsResponse responseForMain = ws.newRequest() | |||
.setParam(PARAM_COMPONENT, project.getKey()) | |||
.executeProtobuf(Ce.AnalysisStatusWsResponse.class); | |||
assertThat(responseForMain.getComponent().getWarningsList()).containsExactly(WARNING_IN_MAIN); | |||
assertThat(responseForMain.getComponent().getWarningsList()) | |||
.extracting(Warning::getKey, Warning::getMessage, Warning::getDismissable) | |||
.containsExactly(tuple(warningInMainMessage.getUuid(), WARNING_IN_MAIN, false)); | |||
Ce.AnalysisStatusWsResponse responseForBranchWithWarning = ws.newRequest() | |||
.setParam(PARAM_COMPONENT, project.getKey()) | |||
.setParam(PARAM_BRANCH, BRANCH_WITH_WARNING) | |||
.executeProtobuf(Ce.AnalysisStatusWsResponse.class); | |||
assertThat(responseForBranchWithWarning.getComponent().getWarningsList()).containsExactly(WARNING_IN_BRANCH); | |||
assertThat(responseForBranchWithWarning.getComponent().getWarningsList()) | |||
.extracting(Warning::getKey, Warning::getMessage, Warning::getDismissable) | |||
.containsExactly(tuple(warningInBranchMessage.getUuid(), WARNING_IN_BRANCH, false)); | |||
Ce.AnalysisStatusWsResponse responseForBranchWithoutWarning = ws.newRequest() | |||
.setParam(PARAM_COMPONENT, project.getKey()) | |||
@@ -265,7 +235,9 @@ public class AnalysisStatusActionTest { | |||
.setParam(PARAM_PULL_REQUEST, PULL_REQUEST) | |||
.executeProtobuf(Ce.AnalysisStatusWsResponse.class); | |||
assertThat(responseForPr.getComponent().getWarningsList()).containsExactly(WARNING_IN_PR); | |||
assertThat(responseForPr.getComponent().getWarningsList()) | |||
.extracting(Warning::getKey, Warning::getMessage, Warning::getDismissable) | |||
.containsExactly(tuple(warningInPrMessage.getUuid(), WARNING_IN_PR, false)); | |||
} | |||
@Test | |||
@@ -304,13 +276,72 @@ public class AnalysisStatusActionTest { | |||
assertThat(responseForPr.getComponent().getPullRequest()).isEqualTo(PULL_REQUEST); | |||
} | |||
private void createTaskMessage(CeActivityDto activity, String warning) { | |||
db.getDbClient().ceTaskMessageDao().insert(db.getSession(), new CeTaskMessageDto() | |||
@Test | |||
public void json_example() { | |||
OrganizationDto organization = db.organizations().insert(o -> o.setKey("my-org-1")); | |||
ComponentDto project = db.components().insertPrivateProject(organization, | |||
p -> p.setDbKey("com.github.kevinsawicki:http-request-parent") | |||
.setName("HttpRequest")); | |||
SnapshotDto analysis = db.components().insertSnapshot(project); | |||
CeActivityDto activity = insertActivity("task-uuid" + counter++, project, SUCCESS, analysis, REPORT); | |||
CeTaskMessageDto ceTaskMessage = new CeTaskMessageDto() | |||
.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) | |||
.setCreatedAt(counter); | |||
db.getDbClient().ceTaskMessageDao().insert(db.getSession(), ceTaskMessage); | |||
db.commit(); | |||
userSession.addProjectPermission(UserRole.USER, project); | |||
String result = ws.newRequest() | |||
.setParam(PARAM_COMPONENT, project.getKey()) | |||
.execute() | |||
.getInput(); | |||
assertJson(result).isSimilarTo(getClass().getResource("analysis_status-example.json")); | |||
} | |||
@Test | |||
public void fail_if_component_key_not_provided() { | |||
expectedException.expect(IllegalArgumentException.class); | |||
ws.newRequest().execute(); | |||
} | |||
@Test | |||
public void fail_if_component_key_is_unknown() { | |||
expectedException.expect(NotFoundException.class); | |||
ws.newRequest().setParam(PARAM_COMPONENT, "nonexistent").execute(); | |||
} | |||
@Test | |||
public void fail_if_both_branch_and_pullRequest_are_specified() { | |||
expectedException.expect(BadRequestException.class); | |||
ws.newRequest() | |||
.setParam(PARAM_COMPONENT, "dummy") | |||
.setParam(PARAM_BRANCH, "feature1") | |||
.setParam(PARAM_PULL_REQUEST, "pr1") | |||
.execute(); | |||
} | |||
private CeTaskMessageDto createTaskMessage(CeActivityDto activity, String warning) { | |||
return createTaskMessage(activity, warning, false); | |||
} | |||
private CeTaskMessageDto createTaskMessage(CeActivityDto activity, String warning, boolean dismissible) { | |||
CeTaskMessageDto ceTaskMessageDto = new CeTaskMessageDto() | |||
.setUuid("m-uuid-" + counter++) | |||
.setTaskUuid(activity.getUuid()) | |||
.setMessage(warning) | |||
.setCreatedAt(counter)); | |||
.setDismissible(dismissible) | |||
.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) { |
@@ -67,10 +67,16 @@ message AnalysisStatusWsResponse { | |||
optional string organization = 1; | |||
optional string key = 2; | |||
optional string name = 3; | |||
repeated string warnings = 4; | |||
repeated Warning warnings = 4; | |||
optional string branch = 5; | |||
optional string pullRequest = 6; | |||
} | |||
message Warning { | |||
optional string key = 1; | |||
optional string message = 2; | |||
optional bool dismissable = 3; | |||
} | |||
} | |||
// GET api/ce/component |