.add(2604, "Add baseline columns in PROJECT_BRANCHES", AddManualBaselineToProjectBranches.class)
.add(2605, "Add SNAPSHOTS.PROJECT_VERSION", AddProjectVersionToSnapshot.class)
.add(2606, "Drop DATA_TYPE column from FILE_SOURCES table", DropDataTypeFromFileSources.class)
- .add(2607, "Add MEMBERS_SYNC_ENABLED column to ORGANIZATIONS_ALM_BINDING table", AddMembersSyncFlagToOrgAlmBinding.class);
+ .add(2607, "Add MEMBERS_SYNC_ENABLED column to ORGANIZATIONS_ALM_BINDING table", AddMembersSyncFlagToOrgAlmBinding.class)
+ .add(2608, "Delete favorites on not supported components", DeleteFavouritesOnNotSupportedComponentQualifiers.class);
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.v77;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.SupportsBlueGreen;
+import org.sonar.server.platform.db.migration.step.DataChange;
+import org.sonar.server.platform.db.migration.step.MassUpdate;
+
+@SupportsBlueGreen
+public class DeleteFavouritesOnNotSupportedComponentQualifiers extends DataChange {
+
+ public DeleteFavouritesOnNotSupportedComponentQualifiers(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ MassUpdate massUpdate = context.prepareMassUpdate().rowPluralName("favourites");
+ massUpdate.select("SELECT prop.id FROM properties prop " +
+ "INNER JOIN projects p ON p.id=prop.resource_id AND p.qualifier NOT IN ('TRK', 'FIL', 'VW', 'SVW', 'APP') " +
+ "WHERE prop_key=? AND user_id IS NOT NULL")
+ .setString(1, "favourite");
+ massUpdate.update("DELETE FROM properties WHERE id=?");
+ massUpdate.execute((row, update) -> {
+ int id = row.getInt(1);
+ update.setInt(1, id);
+ return true;
+ });
+ }
+
+}
@Test
public void verify_migration_count() {
- verifyMigrationCount(underTest, 8);
+ verifyMigrationCount(underTest, 9);
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.v77;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+
+import static java.util.stream.Collectors.toList;
+import static org.apache.commons.lang.math.RandomUtils.nextInt;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DeleteFavouritesOnNotSupportedComponentQualifiersTest {
+
+ private static final String TABLE = "properties";
+ private static final String FAVOURITE_PROPERTY = "favourite";
+
+ @Rule
+ public CoreDbTester db = CoreDbTester.createForSchema(DeleteFavouritesOnNotSupportedComponentQualifiersTest.class, "schema.sql");
+
+ private DeleteFavouritesOnNotSupportedComponentQualifiers underTest = new DeleteFavouritesOnNotSupportedComponentQualifiers(db.database());
+
+ @Test
+ public void delete_favorites_on_none_supported_component_qualifiers() throws SQLException {
+ int moduleId = insertComponent("BRC");
+ insertProperty(FAVOURITE_PROPERTY, moduleId);
+ insertProperty(FAVOURITE_PROPERTY, moduleId);
+ insertProperty(FAVOURITE_PROPERTY, moduleId);
+ int libId = insertComponent("LIB");
+ insertProperty(FAVOURITE_PROPERTY, libId);
+
+ underTest.execute();
+
+ assertThat(db.countRowsOfTable(TABLE)).isZero();
+ }
+
+ @Test
+ public void ignore_favorites_on_supported_component_qualifiers() throws SQLException {
+ int projectId = insertComponent("TRK");
+ int prop1 = insertProperty(FAVOURITE_PROPERTY, projectId);
+ int fileId = insertComponent("FIL");
+ int prop2 = insertProperty(FAVOURITE_PROPERTY, fileId);
+ int portfolioId = insertComponent("VW");
+ int prop3 = insertProperty(FAVOURITE_PROPERTY, portfolioId);
+ int subPortfolioId = insertComponent("SVW");
+ int prop4 = insertProperty(FAVOURITE_PROPERTY, subPortfolioId);
+ int applicationId = insertComponent("APP");
+ int prop5 = insertProperty(FAVOURITE_PROPERTY, applicationId);
+
+ underTest.execute();
+
+ assertProperties(prop1, prop2, prop3, prop4, prop5);
+ }
+
+ @Test
+ public void ignore_other_properties() throws SQLException {
+ int moduleId = insertComponent("BRC");
+ int prop1 = insertProperty("other", moduleId);
+ int prop2 = insertProperty("other", moduleId);
+ int libId = insertComponent("LIB");
+ int prop3 = insertProperty("other", libId);
+
+ underTest.execute();
+
+ assertProperties(prop1, prop2, prop3);
+ }
+
+ private void assertProperties(Integer... expectedIds) {
+ assertThat(db.select("SELECT id FROM properties")
+ .stream()
+ .map(map -> ((Long) map.get("ID")).intValue())
+ .collect(toList()))
+ .containsExactlyInAnyOrder(expectedIds);
+ }
+
+ private int insertProperty(String key, int componentId) {
+ int id = nextInt();
+ db.executeInsert(
+ TABLE,
+ "ID", id,
+ "PROP_KEY", key,
+ "USER_ID", nextInt(),
+ "RESOURCE_ID", componentId,
+ "IS_EMPTY", true,
+ "CREATED_AT", 123456);
+ return id;
+ }
+
+ private int insertComponent(String qualifier) {
+ int id = nextInt();
+ String uuid = "uuid_" + id;
+ db.executeInsert(
+ "projects",
+ "ID", id,
+ "UUID", uuid,
+ "ORGANIZATION_UUID", "org_" + id,
+ "UUID_PATH", "path_" + id,
+ "ROOT_UUID", "root_" + id,
+ "PROJECT_UUID", "project_" + id,
+ "QUALIFIER", qualifier,
+ "ENABLED", true,
+ "PRIVATE", false);
+ return id;
+ }
+}
--- /dev/null
+CREATE TABLE "PROPERTIES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "PROP_KEY" VARCHAR(512) NOT NULL,
+ "RESOURCE_ID" INTEGER,
+ "USER_ID" INTEGER,
+ "IS_EMPTY" BOOLEAN NOT NULL,
+ "TEXT_VALUE" VARCHAR(4000),
+ "CLOB_VALUE" CLOB,
+ "CREATED_AT" BIGINT
+);
+CREATE INDEX "PROPERTIES_KEY" ON "PROPERTIES" ("PROP_KEY");
+
+CREATE TABLE "PROJECTS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "ORGANIZATION_UUID" VARCHAR(40) NOT NULL,
+ "KEE" VARCHAR(400),
+ "UUID" VARCHAR(50) NOT NULL,
+ "UUID_PATH" VARCHAR(1500) NOT NULL,
+ "ROOT_UUID" VARCHAR(50) NOT NULL,
+ "PROJECT_UUID" VARCHAR(50) NOT NULL,
+ "MODULE_UUID" VARCHAR(50),
+ "MODULE_UUID_PATH" VARCHAR(1500),
+ "MAIN_BRANCH_PROJECT_UUID" VARCHAR(50),
+ "NAME" VARCHAR(2000),
+ "DESCRIPTION" VARCHAR(2000),
+ "PRIVATE" BOOLEAN NOT NULL,
+ "TAGS" VARCHAR(500),
+ "ENABLED" BOOLEAN NOT NULL DEFAULT TRUE,
+ "SCOPE" VARCHAR(3),
+ "QUALIFIER" VARCHAR(10),
+ "DEPRECATED_KEE" VARCHAR(400),
+ "PATH" VARCHAR(2000),
+ "LANGUAGE" VARCHAR(20),
+ "COPY_COMPONENT_UUID" VARCHAR(50),
+ "LONG_NAME" VARCHAR(2000),
+ "DEVELOPER_UUID" VARCHAR(50),
+ "CREATED_AT" TIMESTAMP,
+ "AUTHORIZATION_UPDATED_AT" BIGINT,
+ "B_CHANGED" BOOLEAN,
+ "B_COPY_COMPONENT_UUID" VARCHAR(50),
+ "B_DESCRIPTION" VARCHAR(2000),
+ "B_ENABLED" BOOLEAN,
+ "B_UUID_PATH" VARCHAR(1500),
+ "B_LANGUAGE" VARCHAR(20),
+ "B_LONG_NAME" VARCHAR(500),
+ "B_MODULE_UUID" VARCHAR(50),
+ "B_MODULE_UUID_PATH" VARCHAR(1500),
+ "B_NAME" VARCHAR(500),
+ "B_PATH" VARCHAR(2000),
+ "B_QUALIFIER" VARCHAR(10)
+);
+
+CREATE INDEX "PROJECTS_ORGANIZATION" ON "PROJECTS" ("ORGANIZATION_UUID");
+CREATE UNIQUE INDEX "PROJECTS_KEE" ON "PROJECTS" ("KEE");
+CREATE INDEX "PROJECTS_ROOT_UUID" ON "PROJECTS" ("ROOT_UUID");
+CREATE UNIQUE INDEX "PROJECTS_UUID" ON "PROJECTS" ("UUID");
+CREATE INDEX "PROJECTS_PROJECT_UUID" ON "PROJECTS" ("PROJECT_UUID");
+CREATE INDEX "PROJECTS_MODULE_UUID" ON "PROJECTS" ("MODULE_UUID");
+CREATE INDEX "PROJECTS_QUALIFIER" ON "PROJECTS" ("QUALIFIER");
*/
package org.sonar.server.favorite.ws;
+import java.util.List;
import java.util.function.Consumer;
import org.sonar.api.server.ws.Change;
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.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
import org.sonar.server.user.UserSession;
import org.sonar.server.ws.KeyExamples;
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.lang.String.format;
+import static java.lang.String.join;
+import static java.util.Arrays.asList;
+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("TRK", "VW", "APP", "SVW", "FIL");
+ private static final String SUPPORTED_QUALIFIERS_AS_STRING = join(", ", SUPPORTED_QUALIFIERS);
+
private final UserSession userSession;
private final DbClient dbClient;
private final FavoriteUpdater favoriteUpdater;
@Override
public void define(WebService.NewController context) {
WebService.NewAction action = context.createAction("add")
- .setDescription("Add a component (project, directory, file etc.) as favorite for the authenticated user.<br>" +
+ .setDescription("Add a component (project, file etc.) as favorite for the authenticated user.<br>" +
"Requires authentication and the following permission: 'Browse' on the project of the specified component.")
.setSince("6.3")
- .setChangelog(new Change("7.6", String.format("The use of module keys in parameter '%s' is deprecated", PARAM_COMPONENT)))
+ .setChangelog(
+ new Change("7.7", "It's no more possible to set a directory as favorite"),
+ new Change("7.6", format("The use of module keys in parameter '%s' is deprecated", PARAM_COMPONENT)))
.setPost(true)
.setHandler(this);
action.createParam(PARAM_COMPONENT)
- .setDescription("Component key")
+ .setDescription(format("Component key. Only components with qualifiers %s are supported", SUPPORTED_QUALIFIERS_AS_STRING))
.setRequired(true)
.setExampleValue(KeyExamples.KEY_FILE_EXAMPLE_001);
}
return request -> {
try (DbSession dbSession = dbClient.openSession(false)) {
ComponentDto componentDto = componentFinder.getByKey(dbSession, request.mandatoryParam(PARAM_COMPONENT));
+ checkArgument(SUPPORTED_QUALIFIERS.contains(componentDto.qualifier()), "Only components with qualifiers %s are supported", SUPPORTED_QUALIFIERS_AS_STRING);
userSession
.checkLoggedIn()
- .checkComponentPermission(UserRole.USER, componentDto);
+ .checkComponentPermission(USER, componentDto);
favoriteUpdater.add(dbSession, componentDto, userSession.isLoggedIn() ? userSession.getUserId() : null);
dbSession.commit();
}
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.property.PropertyDto;
import org.sonar.db.property.PropertyQuery;
+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 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.web.UserRole.USER;
+import static org.sonar.db.component.ComponentTesting.newDirectory;
import static org.sonar.db.component.ComponentTesting.newFileDto;
-import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
import static org.sonar.server.favorite.ws.FavoritesWsParameters.PARAM_COMPONENT;
public class AddActionTest {
- private static final String PROJECT_KEY = "project-key";
- private static final String PROJECT_UUID = "project-uuid";
- private static final int USER_ID = 123;
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Test
public void add_a_project() {
- ComponentDto project = insertProjectAndPermissions();
+ ComponentDto project = db.components().insertPrivateProject();
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user).addProjectPermission(USER, project);
- TestResponse result = call(PROJECT_KEY);
+ TestResponse result = call(project.getKey());
assertThat(result.getStatus()).isEqualTo(HTTP_NO_CONTENT);
List<PropertyDto> favorites = dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
- .setUserId(USER_ID)
+ .setUserId(user.getId())
.setKey("favourite")
.build(), dbSession);
assertThat(favorites).hasSize(1);
PropertyDto favorite = favorites.get(0);
assertThat(favorite)
.extracting(PropertyDto::getResourceId, PropertyDto::getUserId, PropertyDto::getKey)
- .containsOnly(project.getId(), USER_ID, "favourite");
+ .containsOnly(project.getId(), user.getId(), "favourite");
}
@Test
public void add_a_file() {
- ComponentDto project = insertProjectAndPermissions();
+ ComponentDto project = db.components().insertPrivateProject();
ComponentDto file = db.components().insertComponent(newFileDto(project));
- userSession.addProjectPermission(UserRole.USER, project, file);
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user).addProjectPermission(USER, project);
- call(file.getDbKey());
+ call(file.getKey());
List<PropertyDto> favorites = dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
- .setUserId(USER_ID)
+ .setUserId(user.getId())
.setKey("favourite")
.build(), dbSession);
assertThat(favorites).hasSize(1);
PropertyDto favorite = favorites.get(0);
assertThat(favorite)
.extracting(PropertyDto::getResourceId, PropertyDto::getUserId, PropertyDto::getKey)
- .containsOnly(file.getId(), USER_ID, "favourite");
+ .containsOnly(file.getId(), user.getId(), "favourite");
}
@Test
public void fail_when_no_browse_permission_on_the_project() {
- ComponentDto project = insertProject();
- userSession.logIn();
- userSession.addProjectPermission(UserRole.ADMIN, project);
+ ComponentDto project = db.components().insertPrivateProject();
+ userSession.logIn().addProjectPermission(UserRole.ADMIN, project);
expectedException.expect(ForbiddenException.class);
- call(PROJECT_KEY);
+ call(project.getKey());
}
@Test
@Test
public void fail_when_user_is_not_authenticated() {
- insertProject();
+ ComponentDto project = db.components().insertPrivateProject();
expectedException.expect(UnauthorizedException.class);
- call(PROJECT_KEY);
+ call(project.getKey());
}
@Test
public void fail_when_using_branch_db_key() throws Exception {
OrganizationDto organization = db.organizations().insert();
ComponentDto project = db.components().insertMainBranch(organization);
- userSession.logIn().addProjectPermission(UserRole.USER, project);
ComponentDto branch = db.components().insertProjectBranch(project);
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user).addProjectPermission(USER, project);
expectedException.expect(NotFoundException.class);
expectedException.expectMessage(format("Component key '%s' not found", branch.getDbKey()));
call(branch.getDbKey());
}
+ @Test
+ public void fail_on_directory() {
+ ComponentDto project = db.components().insertPrivateProject();
+ ComponentDto directory = db.components().insertComponent(newDirectory(project, "dir"));
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user).addProjectPermission(USER, project);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Only components with qualifiers TRK, VW, APP, SVW, FIL are supported");
+
+ call(directory.getKey());
+ }
+
@Test
public void definition() {
WebService.Action definition = ws.getDef();
assertThat(definition.param("component").isRequired()).isTrue();
}
- private ComponentDto insertProject() {
- return db.components().insertComponent(newPrivateProjectDto(db.organizations().insert(), PROJECT_UUID).setDbKey(PROJECT_KEY));
- }
-
- private ComponentDto insertProjectAndPermissions() {
- ComponentDto project = insertProject();
- userSession
- .logIn()
- .setUserId(USER_ID)
- .addProjectPermission(UserRole.USER, project);
-
- return project;
- }
-
private TestResponse call(@Nullable String componentKey) {
TestRequest request = ws.newRequest();
ofNullable(componentKey).ifPresent(c -> request.setParam(PARAM_COMPONENT, c));