@@ -780,6 +780,8 @@ public class DbVersion84 implements DbVersion { | |||
.add(3710, "Add primary key on 'UUID' column of 'RULES' table", AddPrimaryKeyOnUuidColumnOfRulesTable.class) | |||
.add(3711, "Drop column 'ID' of 'RULES' table", DropIdColumnOfRulesTable.class) | |||
.add(3800, "Remove favourites for components with qualifiers 'DIR', 'FIL', 'UTS'", RemoveFilesFavouritesFromProperties.class); | |||
; | |||
} | |||
} |
@@ -0,0 +1,48 @@ | |||
/* | |||
* 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.v84; | |||
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 RemoveFilesFavouritesFromProperties extends DataChange { | |||
public RemoveFilesFavouritesFromProperties(Database db) { | |||
super(db); | |||
} | |||
@Override | |||
protected void execute(Context context) throws SQLException { | |||
MassUpdate massUpdate = context.prepareMassUpdate(); | |||
massUpdate.select("select p.uuid from components c " + | |||
"join properties p on c.uuid = p.component_uuid " + | |||
"where p.prop_key = ? and c.qualifier in ('FIL', 'DIR', 'UTS')") | |||
.setString(1, "favourite"); | |||
massUpdate.update("delete from properties where uuid = ?"); | |||
massUpdate.execute((row, update) -> { | |||
String propertyUuid = row.getString(1); | |||
update.setString(1, propertyUuid); | |||
return true; | |||
}); | |||
} | |||
} |
@@ -0,0 +1,147 @@ | |||
/* | |||
* 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.v84; | |||
import java.sql.SQLException; | |||
import java.time.Instant; | |||
import javax.annotation.Nullable; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonar.core.util.Uuids; | |||
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.sonar.api.resources.Qualifiers.APP; | |||
import static org.sonar.api.resources.Qualifiers.DIRECTORY; | |||
import static org.sonar.api.resources.Qualifiers.FILE; | |||
import static org.sonar.api.resources.Qualifiers.PROJECT; | |||
import static org.sonar.api.resources.Qualifiers.UNIT_TEST_FILE; | |||
public class RemoveFilesFavouritesFromPropertiesTest { | |||
private static final String PROPERTIES_TABLE_NAME = "properties"; | |||
@Rule | |||
public CoreDbTester dbTester = CoreDbTester.createForSchema(RemoveFilesFavouritesFromPropertiesTest.class, "schema.sql"); | |||
private DataChange underTest = new RemoveFilesFavouritesFromProperties(dbTester.database()); | |||
private static final String APPLICATION_UUID_1 = Uuids.createFast(); | |||
private static final String PROJECT_UUID_2 = Uuids.createFast(); | |||
private static final String FILE_UUID_3 = Uuids.createFast(); | |||
private static final String DIRECTORY_UUID_4 = Uuids.createFast(); | |||
private static final String UNIT_TEST_FILE_UUID_5 = Uuids.createFast(); | |||
private static final String USER_UUID_1 = "1"; | |||
private static final String USER_UUID_2 = "2"; | |||
@Before | |||
public void setup() { | |||
insertComponent(APPLICATION_UUID_1, APP); | |||
insertComponent(PROJECT_UUID_2, PROJECT); | |||
insertComponent(FILE_UUID_3, FILE); | |||
insertComponent(DIRECTORY_UUID_4, DIRECTORY); | |||
insertComponent(UNIT_TEST_FILE_UUID_5, UNIT_TEST_FILE); | |||
insertProperty("1", USER_UUID_1, APPLICATION_UUID_1, "test"); | |||
insertProperty("2", USER_UUID_2, PROJECT_UUID_2, "test2"); | |||
insertProperty("3", USER_UUID_2, null, "test3"); | |||
} | |||
@Test | |||
public void migrate() throws SQLException { | |||
insertProperty("4", USER_UUID_1, APPLICATION_UUID_1, "favourite"); | |||
// properties to remove | |||
insertProperty("5", USER_UUID_1, FILE_UUID_3, "favourite"); | |||
insertProperty("6", USER_UUID_2, FILE_UUID_3, "favourite"); | |||
insertProperty("7", USER_UUID_2, DIRECTORY_UUID_4, "favourite"); | |||
insertProperty("8", USER_UUID_2, UNIT_TEST_FILE_UUID_5, "favourite"); | |||
underTest.execute(); | |||
assertThat(dbTester.countRowsOfTable(PROPERTIES_TABLE_NAME)).isEqualTo(4); | |||
assertThat(dbTester.select("select uuid from properties").stream().map(columns -> columns.get("UUID"))) | |||
.containsOnly("1", "2", "3", "4"); | |||
} | |||
@Test | |||
public void properties_table_empty() throws SQLException { | |||
dbTester.executeUpdateSql("delete from properties"); | |||
underTest.execute(); | |||
assertThat(dbTester.countRowsOfTable(PROPERTIES_TABLE_NAME)).isEqualTo(0); | |||
} | |||
@Test | |||
public void does_not_remove_properties_with_key_other_than_favourite() throws SQLException { | |||
underTest.execute(); | |||
assertThat(dbTester.countRowsOfTable(PROPERTIES_TABLE_NAME)).isEqualTo(3L); | |||
} | |||
@Test | |||
public void migration_is_re_entrant() throws SQLException { | |||
insertProperty("4", USER_UUID_1, APPLICATION_UUID_1, "favourite"); | |||
// properties to remove | |||
insertProperty("5", USER_UUID_1, FILE_UUID_3, "favourite"); | |||
insertProperty("6", USER_UUID_2, FILE_UUID_3, "favourite"); | |||
underTest.execute(); | |||
insertProperty("7", USER_UUID_2, DIRECTORY_UUID_4, "favourite"); | |||
insertProperty("8", USER_UUID_2, UNIT_TEST_FILE_UUID_5, "favourite"); | |||
// re-entrant | |||
underTest.execute(); | |||
assertThat(dbTester.countRowsOfTable(PROPERTIES_TABLE_NAME)).isEqualTo(4); | |||
assertThat(dbTester.select("select uuid from properties").stream().map(columns -> columns.get("UUID"))) | |||
.containsOnly("1", "2", "3", "4"); | |||
} | |||
private void insertComponent(String uuid, String qualifier) { | |||
dbTester.executeInsert("COMPONENTS", | |||
"UUID", uuid, | |||
"NAME", uuid + "-name", | |||
"DESCRIPTION", uuid + "-description", | |||
"ORGANIZATION_UUID", "default", | |||
"KEE", uuid + "-key", | |||
"PROJECT_UUID", uuid, | |||
"MAIN_BRANCH_PROJECT_UUID", "project_uuid", | |||
"UUID_PATH", ".", | |||
"ROOT_UUID", uuid, | |||
"PRIVATE", Boolean.toString(false), | |||
"SCOPE", "TRK", | |||
"QUALIFIER", qualifier); | |||
} | |||
private void insertProperty(String id, String userUuid, @Nullable String componentUuid, String propKey) { | |||
dbTester.executeInsert(PROPERTIES_TABLE_NAME, | |||
"uuid", id, | |||
"user_uuid", userUuid, | |||
"component_uuid", componentUuid, | |||
"prop_key", propKey, | |||
"is_empty", true, | |||
"created_at", Instant.now().toEpochMilli()); | |||
} | |||
} |
@@ -0,0 +1,55 @@ | |||
CREATE TABLE "COMPONENTS"( | |||
"UUID" VARCHAR(50) NOT NULL, | |||
"ORGANIZATION_UUID" VARCHAR(40) NOT NULL, | |||
"KEE" VARCHAR(400), | |||
"DEPRECATED_KEE" VARCHAR(400), | |||
"NAME" VARCHAR(2000), | |||
"LONG_NAME" VARCHAR(2000), | |||
"DESCRIPTION" VARCHAR(2000), | |||
"ENABLED" BOOLEAN DEFAULT TRUE NOT NULL, | |||
"SCOPE" VARCHAR(3), | |||
"QUALIFIER" VARCHAR(10), | |||
"PRIVATE" BOOLEAN NOT NULL, | |||
"ROOT_UUID" VARCHAR(50) NOT NULL, | |||
"LANGUAGE" VARCHAR(20), | |||
"COPY_COMPONENT_UUID" VARCHAR(50), | |||
"PATH" VARCHAR(2000), | |||
"UUID_PATH" VARCHAR(1500) NOT NULL, | |||
"PROJECT_UUID" VARCHAR(50) NOT NULL, | |||
"MODULE_UUID" VARCHAR(50), | |||
"MODULE_UUID_PATH" VARCHAR(1500), | |||
"MAIN_BRANCH_PROJECT_UUID" VARCHAR(50), | |||
"B_CHANGED" BOOLEAN, | |||
"B_NAME" VARCHAR(500), | |||
"B_LONG_NAME" VARCHAR(500), | |||
"B_DESCRIPTION" VARCHAR(2000), | |||
"B_ENABLED" BOOLEAN, | |||
"B_QUALIFIER" VARCHAR(10), | |||
"B_LANGUAGE" VARCHAR(20), | |||
"B_COPY_COMPONENT_UUID" VARCHAR(50), | |||
"B_PATH" VARCHAR(2000), | |||
"B_UUID_PATH" VARCHAR(1500), | |||
"B_MODULE_UUID" VARCHAR(50), | |||
"B_MODULE_UUID_PATH" VARCHAR(1500), | |||
"CREATED_AT" TIMESTAMP | |||
); | |||
CREATE INDEX "PROJECTS_ORGANIZATION" ON "COMPONENTS"("ORGANIZATION_UUID"); | |||
CREATE UNIQUE INDEX "PROJECTS_KEE" ON "COMPONENTS"("KEE"); | |||
CREATE INDEX "PROJECTS_MODULE_UUID" ON "COMPONENTS"("MODULE_UUID"); | |||
CREATE INDEX "PROJECTS_PROJECT_UUID" ON "COMPONENTS"("PROJECT_UUID"); | |||
CREATE INDEX "PROJECTS_QUALIFIER" ON "COMPONENTS"("QUALIFIER"); | |||
CREATE INDEX "PROJECTS_ROOT_UUID" ON "COMPONENTS"("ROOT_UUID"); | |||
CREATE INDEX "PROJECTS_UUID" ON "COMPONENTS"("UUID"); | |||
CREATE TABLE "PROPERTIES"( | |||
"UUID" VARCHAR(40) NOT NULL, | |||
"PROP_KEY" VARCHAR(512) NOT NULL, | |||
"USER_UUID" VARCHAR(40), | |||
"IS_EMPTY" BOOLEAN NOT NULL, | |||
"TEXT_VALUE" VARCHAR(4000), | |||
"CLOB_VALUE" CLOB(2147483647), | |||
"CREATED_AT" BIGINT NOT NULL, | |||
"COMPONENT_UUID" VARCHAR(40) | |||
); | |||
ALTER TABLE "PROPERTIES" ADD CONSTRAINT "PK_PROPERTIES" PRIMARY KEY("UUID"); | |||
CREATE INDEX "PROPERTIES_KEY" ON "PROPERTIES"("PROP_KEY"); |
@@ -38,17 +38,15 @@ import static java.lang.String.format; | |||
import static java.lang.String.join; | |||
import static java.util.Arrays.asList; | |||
import static org.sonar.api.resources.Qualifiers.APP; | |||
import static org.sonar.api.resources.Qualifiers.FILE; | |||
import static org.sonar.api.resources.Qualifiers.PROJECT; | |||
import static org.sonar.api.resources.Qualifiers.SUBVIEW; | |||
import static org.sonar.api.resources.Qualifiers.UNIT_TEST_FILE; | |||
import static org.sonar.api.resources.Qualifiers.VIEW; | |||
import static org.sonar.api.web.UserRole.USER; | |||
import static org.sonar.server.favorite.ws.FavoritesWsParameters.PARAM_COMPONENT; | |||
public class AddAction implements FavoritesWsAction { | |||
private static final List<String> SUPPORTED_QUALIFIERS = asList(PROJECT, VIEW, SUBVIEW, APP, FILE, UNIT_TEST_FILE); | |||
private static final List<String> SUPPORTED_QUALIFIERS = asList(PROJECT, VIEW, SUBVIEW, APP); | |||
private static final String SUPPORTED_QUALIFIERS_AS_STRING = join(", ", SUPPORTED_QUALIFIERS); | |||
private final UserSession userSession; | |||
@@ -71,6 +69,7 @@ public class AddAction implements FavoritesWsAction { | |||
"Requires authentication and the following permission: 'Browse' on the project of the specified component.") | |||
.setSince("6.3") | |||
.setChangelog( | |||
new Change("8.4", "It's no longer possible to set a file as favorite"), | |||
new Change("7.7", "It's no longer possible to have more than 100 favorites by qualifier"), | |||
new Change("7.7", "It's no longer possible to set a directory as favorite"), | |||
new Change("7.6", format("The use of module keys in parameter '%s' is deprecated", PARAM_COMPONENT))) |
@@ -50,7 +50,7 @@ public class RemoveAction implements FavoritesWsAction { | |||
@Override | |||
public void define(WebService.NewController context) { | |||
WebService.NewAction action = context.createAction("remove") | |||
.setDescription("Remove a component (project, directory, file etc.) as favorite for the authenticated user.<br>" + | |||
.setDescription("Remove a component (project, portfolio, application etc.) as favorite for the authenticated user.<br>" + | |||
"Requires authentication.") | |||
.setSince("6.3") | |||
.setChangelog(new Change("7.6", String.format("The use of module keys in parameter '%s' is deprecated", PARAM_COMPONENT))) | |||
@@ -73,8 +73,7 @@ public class RemoveAction implements FavoritesWsAction { | |||
return request -> { | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
ComponentDto component = componentFinder.getByKey(dbSession, request.mandatoryParam(PARAM_COMPONENT)); | |||
userSession | |||
.checkLoggedIn(); | |||
userSession.checkLoggedIn(); | |||
favoriteUpdater.remove(dbSession, component, userSession.isLoggedIn() ? userSession.getUuid() : null); | |||
dbSession.commit(); | |||
} |
@@ -48,6 +48,7 @@ import static java.lang.String.format; | |||
import static java.net.HttpURLConnection.HTTP_NO_CONTENT; | |||
import static java.util.Optional.ofNullable; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.sonar.api.resources.Qualifiers.UNIT_TEST_FILE; | |||
import static org.sonar.api.web.UserRole.USER; | |||
import static org.sonar.db.component.ComponentTesting.newDirectory; | |||
import static org.sonar.db.component.ComponentTesting.newFileDto; | |||
@@ -88,26 +89,6 @@ public class AddActionTest { | |||
.containsOnly(project.uuid(), user.getUuid(), "favourite"); | |||
} | |||
@Test | |||
public void add_a_file() { | |||
ComponentDto project = db.components().insertPrivateProject(); | |||
ComponentDto file = db.components().insertComponent(newFileDto(project)); | |||
UserDto user = db.users().insertUser(); | |||
userSession.logIn(user).addProjectPermission(USER, project); | |||
call(file.getKey()); | |||
List<PropertyDto> favorites = dbClient.propertiesDao().selectByQuery(PropertyQuery.builder() | |||
.setUserUuid(user.getUuid()) | |||
.setKey("favourite") | |||
.build(), dbSession); | |||
assertThat(favorites).hasSize(1); | |||
PropertyDto favorite = favorites.get(0); | |||
assertThat(favorite) | |||
.extracting(PropertyDto::getComponentUuid, PropertyDto::getUserUuid, PropertyDto::getKey) | |||
.containsOnly(file.uuid(), user.getUuid(), "favourite"); | |||
} | |||
@Test | |||
public void fail_when_no_browse_permission_on_the_project() { | |||
ComponentDto project = db.components().insertPrivateProject(); | |||
@@ -158,11 +139,37 @@ public class AddActionTest { | |||
userSession.logIn(user).addProjectPermission(USER, project); | |||
expectedException.expect(IllegalArgumentException.class); | |||
expectedException.expectMessage("Only components with qualifiers TRK, VW, SVW, APP, FIL, UTS are supported"); | |||
expectedException.expectMessage("Only components with qualifiers TRK, VW, SVW, APP are supported"); | |||
call(directory.getKey()); | |||
} | |||
@Test | |||
public void fail_on_file() { | |||
ComponentDto project = db.components().insertPrivateProject(); | |||
ComponentDto file = db.components().insertComponent(newFileDto(project)); | |||
UserDto user = db.users().insertUser(); | |||
userSession.logIn(user).addProjectPermission(USER, project); | |||
expectedException.expect(IllegalArgumentException.class); | |||
expectedException.expectMessage("Only components with qualifiers TRK, VW, SVW, APP are supported"); | |||
call(file.getKey()); | |||
} | |||
@Test | |||
public void fail_on_unit_test_file() { | |||
ComponentDto project = db.components().insertPrivateProject(); | |||
ComponentDto unitTestFile = db.components().insertComponent(newFileDto(project).setQualifier(UNIT_TEST_FILE)); | |||
UserDto user = db.users().insertUser(); | |||
userSession.logIn(user).addProjectPermission(USER, project); | |||
expectedException.expect(IllegalArgumentException.class); | |||
expectedException.expectMessage("Only components with qualifiers TRK, VW, SVW, APP are supported"); | |||
call(unitTestFile.getKey()); | |||
} | |||
@Test | |||
public void definition() { | |||
WebService.Action definition = ws.getDef(); |