]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8115 Storing default qgate in table (blue green deploy safety) (#1925)
authorJacek <52388493+jacek-poreda-sonarsource@users.noreply.github.com>
Fri, 19 Jul 2019 08:59:07 +0000 (10:59 +0200)
committerSonarTech <sonartech@sonarsource.com>
Fri, 19 Jul 2019 18:21:15 +0000 (20:21 +0200)
* add DDL for project_qgate table

* support saving/update/delete to project quality gate and properties

* add db migration tests

21 files changed:
server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl
server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.xml
server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/qualitygate/ProjectQgateAssociationDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/qualitygate/QualityGateDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/qualitygate/QualityGateDbTester.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v80/CreateProjectQualityGatesTable.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v80/DbVersion80.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v80/CreateProjectQualityGatesTableTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v80/DbVersion80Test.java
server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateUpdater.java
server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/DeselectAction.java
server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/DestroyAction.java
server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/SelectAction.java
server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/DeselectActionTest.java
server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/DestroyActionTest.java
server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/SelectActionTest.java

index b4553e23eaa77569d0fde48cf476631d159db35c..af7c91f6c75af155e28a9f0f2fda3ddf1478654a 100644 (file)
@@ -313,6 +313,14 @@ CREATE TABLE "ORG_QUALITY_GATES" (
 );
 CREATE UNIQUE INDEX "UNIQ_ORG_QUALITY_GATES" ON "ORG_QUALITY_GATES" ("ORGANIZATION_UUID","QUALITY_GATE_UUID");
 
+CREATE TABLE "PROJECT_QGATES" (
+"PROJECT_UUID" VARCHAR(40) NOT NULL,
+"QUALITY_GATE_UUID" VARCHAR(40) NOT NULL,
+
+CONSTRAINT "PK_PROJECT_QGATES" PRIMARY KEY ("PROJECT_UUID")
+);
+CREATE UNIQUE INDEX "UNIQ_PROJECT_QGATES" ON "PROJECT_QGATES" ("PROJECT_UUID", "QUALITY_GATE_UUID");
+
 CREATE TABLE "PROPERTIES" (
   "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
   "PROP_KEY" VARCHAR(512) NOT NULL,
index 1cd23ae5d46590c108bf24104a05fb6e0bfb3ec4..9e9152935fee569c04567d08f804b8e1c8902d09 100644 (file)
@@ -40,8 +40,32 @@ public class ProjectQgateAssociationDao implements Dao {
     return id == null ? Optional.empty() : Optional.of(Long.valueOf(id));
   }
 
+  /**
+   * @return quality gate uuid if a specific Quality Gate has been defined for the given component uuid. <br>
+   * Returns <code>{@link Optional#empty()}</code> otherwise (ex: default quality gate applies)
+   */
+  public Optional<String> selectQGateUuidByComponentUuid(DbSession dbSession, String componentUuid) {
+    String uuid = mapper(dbSession).selectQGateUuidByComponentUuid(componentUuid);
+    return Optional.ofNullable(uuid);
+  }
+
   private static ProjectQgateAssociationMapper mapper(DbSession session) {
     return session.getMapper(ProjectQgateAssociationMapper.class);
   }
 
+  public void deleteByProjectUuid(DbSession dbSession, String projectUuid) {
+    mapper(dbSession).deleteByProjectUuid(projectUuid);
+  }
+
+  public void deleteByQGateUuid(DbSession dbSession, String qGateUuid) {
+    mapper(dbSession).deleteByQGateUuid(qGateUuid);
+  }
+
+  public void insertProjectQGateAssociation(DbSession dbSession, String projectUuid, String qGateUuid) {
+    mapper(dbSession).insertProjectQGateAssociation(projectUuid, qGateUuid);
+  }
+
+  public void updateProjectQGateAssociation(DbSession dbSession, String projectUuid, String qGateUuid) {
+    mapper(dbSession).updateProjectQGateAssociation(projectUuid, qGateUuid);
+  }
 }
index d1f8ab689f546cac00c0028d0f61b3c8afc3f1dc..4d282c6a0919b06dac2b8c927585a36ce4b40069 100644 (file)
@@ -29,4 +29,16 @@ public interface ProjectQgateAssociationMapper {
 
   @CheckForNull
   String selectQGateIdByComponentId(long componentId);
+
+  @CheckForNull
+  String selectQGateUuidByComponentUuid(String componentUuid);
+
+  void deleteByProjectUuid(String projectUuid);
+
+  void insertProjectQGateAssociation(@Param("projectUuid") String projectUuid, @Param("qGateUuid") String qGateUuid);
+
+  void deleteByQGateUuid(String qGateUuid);
+
+  void updateProjectQGateAssociation(@Param("projectUuid") String projectUuid, @Param("qGateUuid") String qGateUuid);
+
 }
index b73dd5ceb965e898467d2bde82edd92667f1c9f6..cc9e7a2b415e271d551405c4d9e384ad9839d563 100644 (file)
@@ -101,4 +101,8 @@ public class QualityGateDao implements Dao {
   private static QualityGateMapper mapper(DbSession session) {
     return session.getMapper(QualityGateMapper.class);
   }
+
+  public QualityGateDto selectByProjectUuid(DbSession dbSession, String uuid) {
+    return mapper(dbSession).selectByProjectUuid(uuid);
+  }
 }
index bdcc9c121f4df1666b7a2a87456d5c19a3ef20ff..490f3bbbadc0f81c367b3415ca3b07709924a515 100644 (file)
@@ -56,4 +56,6 @@ public interface QualityGateMapper {
   void update(QualityGateDto qGate);
 
   void ensureOneBuiltInQualityGate(String builtInQualityName);
+
+  QualityGateDto selectByProjectUuid(@Param("projectUuid") String projectUuid);
 }
index 5d9239182ed56bb4c395fc2f58f87d9cbb9737e7..ac1a2960ac8ba23c56c68fc7630b94d3cea2fc7e 100644 (file)
     </where>
   </select>
 
+  <select id="selectQGateUuidByComponentUuid" parameterType="String" resultType="string">
+    SELECT quality_gate_uuid
+    FROM project_qgates
+    <where>
+      AND project_uuid=#{componentUuid}
+    </where>
+  </select>
+
+  <delete id="deleteByProjectUuid" parameterType="String">
+    DELETE
+    FROM project_qgates
+    WHERE
+    project_uuid=#{uuid,jdbcType=VARCHAR}
+  </delete>
+
+  <delete id="deleteByQGateUuid" parameterType="String">
+    DELETE
+    FROM project_qgates
+    WHERE
+    quality_gate_uuid=#{uuid,jdbcType=VARCHAR}
+  </delete>
+
+  <insert id="insertProjectQGateAssociation" parameterType="map">
+    INSERT into project_qgates
+    (
+    project_uuid,
+    quality_gate_uuid
+    )
+    VALUES (
+    #{projectUuid,jdbcType=VARCHAR},
+    #{qGateUuid,jdbcType=VARCHAR}
+    )
+  </insert>
+
+  <update id="updateProjectQGateAssociation" parameterType="map">
+    UPDATE project_qgates
+    SET
+    quality_gate_uuid=#{qGateUuid,jdbcType=VARCHAR}
+    WHERE
+    project_uuid = #{projectUuid,jdbcType=VARCHAR}
+  </update>
+
 </mapper>
index f7ea50278a6d1540488a6b588a10cebe6c1bef04..4d46494c2ff3c23d65f72698117b028e20f28d33 100644 (file)
       qg.id = #{id, jdbcType=BIGINT}
   </select>
 
+  <select id="selectByProjectUuid" parameterType="Map" resultType="org.sonar.db.qualitygate.QualityGateDto">
+    SELECT
+    <include refid="gateColumns"/>
+    FROM
+    quality_gates qg
+    INNER JOIN
+    project_qgates pqg ON pqg.quality_gate_uuid = qg.uuid AND pqg.project_uuid = #{projectUuid, jdbcType=VARCHAR}
+  </select>
+
   <select id="selectById" parameterType="long" resultType="QualityGate">
     select
     <include refid="gateColumns"/>
index 2f0a99162fab4c6336699ce21bb0102fc0ae6e97..77dae8512840084912a6b2e3640ae0b1bce20cf7 100644 (file)
@@ -77,17 +77,17 @@ public class ProjectQgateAssociationDaoTest {
       .qualityGate(qualityGate)
       .membership(ProjectQgateAssociationQuery.IN)
       .build()))
-        .extracting(ProjectQgateAssociationDto::getId, ProjectQgateAssociationDto::getName, ProjectQgateAssociationDto::getGateId)
-        .containsExactlyInAnyOrder(
-          tuple(project1.getId(), project1.name(), qualityGate.getId().toString()),
-          tuple(project2.getId(), project2.name(), qualityGate.getId().toString()));
+      .extracting(ProjectQgateAssociationDto::getId, ProjectQgateAssociationDto::getName, ProjectQgateAssociationDto::getGateId)
+      .containsExactlyInAnyOrder(
+        tuple(project1.getId(), project1.name(), qualityGate.getId().toString()),
+        tuple(project2.getId(), project2.name(), qualityGate.getId().toString()));
 
     assertThat(underTest.selectProjects(dbSession, ProjectQgateAssociationQuery.builder()
       .qualityGate(qualityGate)
       .membership(ProjectQgateAssociationQuery.OUT)
       .build()))
-        .extracting(ProjectQgateAssociationDto::getId, ProjectQgateAssociationDto::getName, ProjectQgateAssociationDto::getGateId)
-        .containsExactlyInAnyOrder(tuple(project3.getId(), project3.name(), null));
+      .extracting(ProjectQgateAssociationDto::getId, ProjectQgateAssociationDto::getName, ProjectQgateAssociationDto::getGateId)
+      .containsExactlyInAnyOrder(tuple(project3.getId(), project3.name(), null));
   }
 
   @Test
@@ -104,15 +104,15 @@ public class ProjectQgateAssociationDaoTest {
       .qualityGate(qualityGate)
       .projectSearch("one")
       .build()))
-        .extracting(ProjectQgateAssociationDto::getId)
-        .containsExactlyInAnyOrder(project1.getId());
+      .extracting(ProjectQgateAssociationDto::getId)
+      .containsExactlyInAnyOrder(project1.getId());
 
     assertThat(underTest.selectProjects(dbSession, ProjectQgateAssociationQuery.builder()
       .qualityGate(qualityGate)
       .projectSearch("project")
       .build()))
-        .extracting(ProjectQgateAssociationDto::getId)
-        .containsExactlyInAnyOrder(project1.getId(), project2.getId(), project3.getId());
+      .extracting(ProjectQgateAssociationDto::getId)
+      .containsExactlyInAnyOrder(project1.getId(), project2.getId(), project3.getId());
   }
 
   @Test
@@ -126,8 +126,8 @@ public class ProjectQgateAssociationDaoTest {
     assertThat(underTest.selectProjects(dbSession, ProjectQgateAssociationQuery.builder()
       .qualityGate(qualityGate)
       .build()))
-        .extracting(ProjectQgateAssociationDto::getId)
-        .containsExactly(project1.getId(), project3.getId(), project2.getId());
+      .extracting(ProjectQgateAssociationDto::getId)
+      .containsExactly(project1.getId(), project3.getId(), project2.getId());
   }
 
   @Test
@@ -174,4 +174,63 @@ public class ProjectQgateAssociationDaoTest {
     assertThat(result).contains(qualityGate1.getId());
   }
 
+  @Test
+  public void select_qgate_uuid_by_component_uuid() {
+    OrganizationDto organization = db.organizations().insert();
+    QGateWithOrgDto qualityGate = db.qualityGates().insertQualityGate(organization);
+    ComponentDto project = db.components().insertPrivateProject(organization);
+
+    db.qualityGates().associateProjectToQualityGate(project, qualityGate);
+
+    Optional<String> qGateUuid = underTest.selectQGateUuidByComponentUuid(dbSession, project.uuid());
+
+    assertThat(qGateUuid).contains(qualityGate.getUuid());
+  }
+
+
+  @Test
+  public void delete_by_project_uuid() {
+    OrganizationDto organization = db.organizations().insert();
+    QGateWithOrgDto qualityGate = db.qualityGates().insertQualityGate(organization);
+    ComponentDto project = db.components().insertPrivateProject(organization);
+
+    db.qualityGates().associateProjectToQualityGate(project, qualityGate);
+
+    underTest.deleteByProjectUuid(dbSession, project.uuid());
+
+    Optional<String> deletedQualityGate = db.qualityGates().selectQGateUuidByComponentUuid(project.uuid());
+
+    assertThat(deletedQualityGate).isEmpty();
+  }
+
+  @Test
+  public void delete_by_qgate_uuid() {
+    OrganizationDto organization = db.organizations().insert();
+    QGateWithOrgDto qualityGate = db.qualityGates().insertQualityGate(organization);
+    ComponentDto project = db.components().insertPrivateProject(organization);
+
+    db.qualityGates().associateProjectToQualityGate(project, qualityGate);
+
+    underTest.deleteByQGateUuid(dbSession, qualityGate.getUuid());
+
+    Optional<String> deletedQualityGate = db.qualityGates().selectQGateUuidByComponentUuid(project.uuid());
+
+    assertThat(deletedQualityGate).isEmpty();
+  }
+
+  @Test
+  public void update_project_qgate_association() {
+    OrganizationDto organization = db.organizations().insert();
+    QGateWithOrgDto firstQualityGate = db.qualityGates().insertQualityGate(organization);
+    QGateWithOrgDto secondQualityGate = db.qualityGates().insertQualityGate(organization);
+    ComponentDto project = db.components().insertPrivateProject(organization);
+
+    db.qualityGates().associateProjectToQualityGate(project, firstQualityGate);
+
+    underTest.updateProjectQGateAssociation(dbSession, project.uuid(), secondQualityGate.getUuid());
+
+    Optional<String> updatedQualityGateUuid = db.qualityGates().selectQGateUuidByComponentUuid(project.uuid());
+
+    assertThat(updatedQualityGateUuid).contains(secondQualityGate.getUuid());
+  }
 }
index 63e588e22497dbdcda613deb7e1f23425485df14..824390141e570f65bf171002d8e10a7a5fc6712e 100644 (file)
@@ -27,6 +27,7 @@ import org.sonar.api.utils.System2;
 import org.sonar.core.util.Uuids;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
 import org.sonar.db.organization.OrganizationDto;
 
 import static java.lang.String.format;
@@ -110,6 +111,19 @@ public class QualityGateDaoTest {
     assertThat(underTest.selectById(dbSession, -1L)).isNull();
   }
 
+  @Test
+  public void select_by_uuid() {
+    QGateWithOrgDto dto = qualityGateDbTester.insertQualityGate(db.getDefaultOrganization(), g -> g.setName("QG Name").setBuiltIn(false));
+    QualityGateDto qualityGateToAssociate = underTest.selectById(dbSession, dto.getId());
+    ComponentDto project = db.components().insertPrivateProject();
+
+    qualityGateDbTester.associateProjectToQualityGate(project, qualityGateToAssociate);
+
+    QualityGateDto qualityGateFromSelect = underTest.selectByProjectUuid(dbSession, project.uuid());
+
+    assertThat(qualityGateFromSelect.getUuid()).isEqualTo(qualityGateToAssociate.getUuid());
+  }
+
   @Test
   public void select_by_organization_and_uuid() {
     OrganizationDto organization = db.organizations().insert();
index 872e495e950142a4b6d42039e8992de9bc5ffe3e..01727b4c215c489064d9f20d42e9686dc7943907 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.db.qualitygate;
 
 import java.util.Arrays;
 import java.util.Date;
+import java.util.Optional;
 import java.util.function.Consumer;
 import org.sonar.core.util.Uuids;
 import org.sonar.db.DbClient;
@@ -74,6 +75,9 @@ public class QualityGateDbTester {
       .setKey("sonar.qualitygate")
       .setResourceId(component.getId())
       .setValue(String.valueOf(qualityGate.getId())));
+
+    dbClient.projectQgateAssociationDao().insertProjectQGateAssociation(dbSession, component.uuid(), qualityGate.getUuid());
+
     db.commit();
   }
 
@@ -105,4 +109,8 @@ public class QualityGateDbTester {
     db.commit();
     return condition;
   }
+
+  public Optional<String> selectQGateUuidByComponentUuid(String uuid) {
+    return dbClient.projectQgateAssociationDao().selectQGateUuidByComponentUuid(dbSession, uuid);
+  }
 }
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v80/CreateProjectQualityGatesTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v80/CreateProjectQualityGatesTable.java
new file mode 100644 (file)
index 0000000..f8a7615
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * 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.v80;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.DatabaseUtils;
+import org.sonar.server.platform.db.migration.SupportsBlueGreen;
+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.VarcharColumnDef.UUID_SIZE;
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+@SupportsBlueGreen
+public class CreateProjectQualityGatesTable extends DdlChange {
+
+  private static final String TABLE_NAME = "project_qgates";
+
+  private static final VarcharColumnDef PROJECT_UUID_COLUMN = newVarcharColumnDefBuilder()
+    .setColumnName("project_uuid")
+    .setIsNullable(false)
+    .setLimit(UUID_SIZE)
+    .build();
+  private static final VarcharColumnDef QUALITY_GATE_UUID_COLUMN = newVarcharColumnDefBuilder()
+    .setColumnName("quality_gate_uuid")
+    .setIsNullable(false)
+    .setLimit(UUID_SIZE)
+    .build();
+
+  public CreateProjectQualityGatesTable(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    if (tableExists()) {
+      return;
+    }
+    context.execute(new CreateTableBuilder(getDialect(), TABLE_NAME)
+      .addPkColumn(PROJECT_UUID_COLUMN)
+      .addColumn(QUALITY_GATE_UUID_COLUMN)
+      .build());
+
+    context.execute(new CreateIndexBuilder()
+      .setTable(TABLE_NAME)
+      .setName("uniq_project_qgates")
+      .setUnique(true)
+      .addColumn(PROJECT_UUID_COLUMN)
+      .addColumn(QUALITY_GATE_UUID_COLUMN)
+      .build());
+  }
+
+  private boolean tableExists() throws SQLException {
+    try (Connection connection = getDatabase().getDataSource().getConnection()) {
+      return DatabaseUtils.tableExists(TABLE_NAME, connection);
+    }
+  }
+}
index a2f5956c7bb09242f53d49d201ec88b03007576d..f3f0a818d1e280871a288f7c43eb50239754fc40 100644 (file)
@@ -26,6 +26,7 @@ public class DbVersion80 implements DbVersion {
   @Override
   public void addSteps(MigrationStepRegistry registry) {
     registry
-      .add(3000, "Set Organizations#guarded column nullable", MakeOrganizationsGuardedNullable.class);
+      .add(3000, "Set Organizations#guarded column nullable", MakeOrganizationsGuardedNullable.class)
+      .add(3001, "Create ProjectQualityGates table", CreateProjectQualityGatesTable.class);
   }
 }
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v80/CreateProjectQualityGatesTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v80/CreateProjectQualityGatesTableTest.java
new file mode 100644 (file)
index 0000000..72b5075
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * 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.v80;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.db.CoreDbTester;
+
+import static java.sql.Types.VARCHAR;
+
+public class CreateProjectQualityGatesTableTest {
+  private static final String TABLE_NAME = "project_qgates";
+
+  @Rule
+  public CoreDbTester dbTester = CoreDbTester.createEmpty();
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private CreateProjectQualityGatesTable underTest = new CreateProjectQualityGatesTable(dbTester.database());
+
+  @Test
+  public void table_has_been_created() throws SQLException {
+    underTest.execute();
+
+    dbTester.assertTableExists(TABLE_NAME);
+    dbTester.assertPrimaryKey(TABLE_NAME, "pk_project_qgates", "project_uuid");
+    dbTester.assertUniqueIndex(TABLE_NAME, "uniq_project_qgates", "project_uuid", "quality_gate_uuid");
+
+    dbTester.assertColumnDefinition(TABLE_NAME, "project_uuid", VARCHAR, 40, false);
+    dbTester.assertColumnDefinition(TABLE_NAME, "quality_gate_uuid", VARCHAR, 40, false);
+
+    //script should not fail if executed twice
+    underTest.execute();
+  }
+}
index 9e64b803eea4222cf8ce2b407f7571325445b32a..3552b825d633b1059f7c36208d5a1a6799825fdf 100644 (file)
@@ -21,7 +21,6 @@ package org.sonar.server.platform.db.migration.version.v80;
 
 import org.junit.Test;
 import org.sonar.server.platform.db.migration.version.DbVersion;
-import org.sonar.server.platform.db.migration.version.v79.DbVersion79;
 
 import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMigrationCount;
 import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMinimumMigrationNumber;
@@ -36,7 +35,7 @@ public class DbVersion80Test {
 
   @Test
   public void verify_migration_count() {
-    verifyMigrationCount(underTest, 1);
+    verifyMigrationCount(underTest, 2);
   }
 
 }
index 8fa1bc6846485f3f975c98f17961cc7141888107..93658d2aa270480159d373fd0ef00597a79530f3 100644 (file)
@@ -22,14 +22,11 @@ package org.sonar.server.qualitygate;
 import org.sonar.core.util.UuidFactory;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
-import org.sonar.db.component.ComponentDto;
 import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.property.PropertyDto;
 import org.sonar.db.qualitygate.QualityGateConditionDto;
 import org.sonar.db.qualitygate.QualityGateDto;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static org.sonar.server.qualitygate.QualityGateFinder.SONAR_QUALITYGATE_PROPERTY;
 import static org.sonar.server.util.Validation.IS_ALREADY_USED_MESSAGE;
 
 public class QualityGateUpdater {
@@ -78,17 +75,6 @@ public class QualityGateUpdater {
     dbClient.qualityGateDao().update(qualityGateDto, dbSession);
   }
 
-  public void dissociateProject(DbSession dbSession, ComponentDto project) {
-    dbClient.propertiesDao().deleteProjectProperty(SONAR_QUALITYGATE_PROPERTY, project.getId(), dbSession);
-  }
-
-  public void associateProject(DbSession dbSession, ComponentDto project, QualityGateDto qualityGate) {
-    dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto()
-      .setKey(SONAR_QUALITYGATE_PROPERTY)
-      .setResourceId(project.getId())
-      .setValue(String.valueOf(qualityGate.getId())));
-  }
-
   private void checkQualityGateDoesNotAlreadyExist(DbSession dbSession, OrganizationDto organizationDto, String name) {
     QualityGateDto existingQgate = dbClient.qualityGateDao().selectByOrganizationAndName(dbSession, organizationDto, name);
     checkArgument(existingQgate == null, IS_ALREADY_USED_MESSAGE, "Name");
index d5a5392df6ba3ea0622c7ef1217b89a99734375d..8bcf5a39e78592e53c90ae272c3975f7743e42e0 100644 (file)
@@ -89,6 +89,7 @@ public class DeselectAction implements QualityGatesWsAction {
   private void dissociateProject(DbSession dbSession, OrganizationDto organization, ComponentDto project) {
     wsSupport.checkCanAdminProject(organization, project);
     dbClient.propertiesDao().deleteProjectProperty(SONAR_QUALITYGATE_PROPERTY, project.getId(), dbSession);
+    dbClient.projectQgateAssociationDao().deleteByProjectUuid(dbSession, project.uuid());
     dbSession.commit();
   }
 
@@ -106,7 +107,7 @@ public class DeselectAction implements QualityGatesWsAction {
 
     try {
       long dbId = Long.parseLong(projectId);
-      return Optional.ofNullable(dbClient.componentDao().selectById(dbSession, dbId).orElse(null));
+      return dbClient.componentDao().selectById(dbSession, dbId);
     } catch (NumberFormatException e) {
       return Optional.empty();
     }
index e92798705653ddf1fcbe7906660fd2c2cff366d4..ce098eaa65f86981e0474a9f60535b29e21612c8 100644 (file)
@@ -72,6 +72,7 @@ public class DestroyAction implements QualityGatesWsAction {
       wsSupport.checkCanEdit(qualityGate);
 
       dbClient.propertiesDao().deleteByKeyAndValue(dbSession, SONAR_QUALITYGATE_PROPERTY, String.valueOf(qualityGate.getId()));
+      dbClient.projectQgateAssociationDao().deleteByQGateUuid(dbSession, qualityGate.getUuid());
       dbClient.qualityGateDao().delete(qualityGate, dbSession);
       dbSession.commit();
       response.noContent();
index 71c38ad77a22405f208fb0f3425762e4cbc45ecb..e8fc3e5eb641d7bed7d0b881728190aa5b849c10 100644 (file)
@@ -31,6 +31,7 @@ import org.sonar.db.component.ComponentDto;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.property.PropertyDto;
 import org.sonar.db.qualitygate.QGateWithOrgDto;
+import org.sonar.db.qualitygate.QualityGateDto;
 import org.sonar.server.component.ComponentFinder;
 import org.sonar.server.component.ComponentFinder.ParamNames;
 
@@ -99,6 +100,16 @@ public class SelectAction implements QualityGatesWsAction {
         .setResourceId(project.getId())
         .setValue(String.valueOf(qualityGate.getId())));
 
+      QualityGateDto currentQualityGate = dbClient.qualityGateDao().selectByProjectUuid(dbSession, project.uuid());
+      if (currentQualityGate == null) {
+        // project uses the default profile
+        dbClient.projectQgateAssociationDao()
+          .insertProjectQGateAssociation(dbSession, project.uuid(), qualityGate.getUuid());
+      } else if (!qualityGate.getUuid().equals(currentQualityGate.getUuid())) {
+        dbClient.projectQgateAssociationDao()
+          .updateProjectQGateAssociation(dbSession, project.uuid(), qualityGate.getUuid());
+      }
+
       dbSession.commit();
     }
     response.noContent();
@@ -118,7 +129,7 @@ public class SelectAction implements QualityGatesWsAction {
 
     try {
       long dbId = Long.parseLong(projectId);
-      return Optional.ofNullable(dbClient.componentDao().selectById(dbSession, dbId).orElse(null));
+      return dbClient.componentDao().selectById(dbSession, dbId);
     } catch (NumberFormatException e) {
       return Optional.empty();
     }
index e3ced0d69dc62aaa3a26e09464cbce82e183fc9d..8ef3fedbd48e84100cffa09cfcf08a10787cb3af 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.qualitygate.ws;
 
+import java.util.Optional;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -77,7 +78,7 @@ public class DeselectActionTest {
       .setParam("organization", organization.getKey())
       .execute();
 
-    assertDeselected(project.getId());
+    assertDeselected(project);
   }
 
   @Test
@@ -93,7 +94,7 @@ public class DeselectActionTest {
       .setParam("organization", organization.getKey())
       .execute();
 
-    assertDeselected(project.getId());
+    assertDeselected(project);
   }
 
   @Test
@@ -109,7 +110,7 @@ public class DeselectActionTest {
       .setParam("organization", organization.getKey())
       .execute();
 
-    assertDeselected(project.getId());
+    assertDeselected(project);
   }
 
   @Test
@@ -125,7 +126,7 @@ public class DeselectActionTest {
       .setParam("organization", organization.getKey())
       .execute();
 
-    assertDeselected(project.getId());
+    assertDeselected(project);
   }
 
   @Test
@@ -134,7 +135,6 @@ public class DeselectActionTest {
     userSession.addPermission(ADMINISTER_QUALITY_GATES, organization);
     QGateWithOrgDto qualityGate = db.qualityGates().insertQualityGate(organization);
     ComponentDto project = db.components().insertPrivateProject(organization);
-    String gateId = valueOf(qualityGate.getId());
     associateProjectToQualityGate(project, qualityGate);
     // Another project
     ComponentDto anotherProject = db.components().insertPrivateProject(organization);
@@ -145,8 +145,8 @@ public class DeselectActionTest {
       .setParam("organization", organization.getKey())
       .execute();
 
-    assertDeselected(project.getId());
-    assertSelected(gateId, anotherProject.getId());
+    assertDeselected(project);
+    assertSelected(qualityGate, anotherProject);
   }
 
   @Test
@@ -161,7 +161,7 @@ public class DeselectActionTest {
       .setParam("projectKey", project.getKey())
       .execute();
 
-    assertDeselected(project.getId());
+    assertDeselected(project);
   }
 
   @Test
@@ -304,14 +304,26 @@ public class DeselectActionTest {
       .setResourceId(project.getId())
       .setValue(qualityGate.getId().toString())
       .setKey(SONAR_QUALITYGATE_PROPERTY));
+    db.qualityGates().associateProjectToQualityGate(project, qualityGate);
     db.commit();
   }
 
-  private void assertDeselected(long projectId) {
-    assertThat(dbClient.propertiesDao().selectProjectProperty(projectId, SONAR_QUALITYGATE_PROPERTY)).isNull();
+  private void assertDeselected(ComponentDto project) {
+    Optional<String> qGateUuid = db.qualityGates().selectQGateUuidByComponentUuid(project.uuid());
+    assertThat(qGateUuid)
+      .isNotNull()
+      .isEmpty();
+
+    assertThat(dbClient.propertiesDao().selectProjectProperty(project.getId(), SONAR_QUALITYGATE_PROPERTY)).isNull();
   }
 
-  private void assertSelected(String qGateId, long projectId) {
-    assertThat(dbClient.propertiesDao().selectProjectProperty(projectId, SONAR_QUALITYGATE_PROPERTY).getValue()).isEqualTo(qGateId);
+  private void assertSelected(QGateWithOrgDto qualityGate, ComponentDto project) {
+    Optional<String> qGateUuid = db.qualityGates().selectQGateUuidByComponentUuid(project.uuid());
+    assertThat(qGateUuid)
+      .isNotNull()
+      .isNotEmpty()
+      .hasValue(qualityGate.getUuid());
+    String qGateId = dbClient.propertiesDao().selectProjectProperty(project.getId(), SONAR_QUALITYGATE_PROPERTY).getValue();
+    assertThat(qGateId).isEqualTo(String.valueOf(qualityGate.getId()));
   }
 }
index f11a9191242236eacfa36cdaa2cd1a3784c0b158..9fe569ccaf2b65f062c4e64726e21b1444851d3a 100644 (file)
@@ -119,6 +119,11 @@ public class DestroyActionTest {
       .isEmpty();
     assertThat(db.getDbClient().propertiesDao().selectProjectProperties(prj2.getDbKey()))
       .isEmpty();
+
+    assertThat(db.getDbClient().projectQgateAssociationDao().selectQGateUuidByComponentUuid(dbSession, prj1.uuid()))
+      .isEmpty();
+    assertThat(db.getDbClient().projectQgateAssociationDao().selectQGateUuidByComponentUuid(dbSession, prj2.uuid()))
+      .isEmpty();
   }
 
   @Test
index 4c53dcb6a7f6e897dad15c84c132fd9f99f53fbd..d38f148f5c8704069fa0dd1175477123eaf8fd9c 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.qualitygate.ws;
 
+import java.util.Optional;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -105,6 +106,51 @@ public class SelectActionTest {
     assertSelected(qualityGate, project);
   }
 
+  @Test
+  public void change_quality_gate_for_project() {
+    OrganizationDto organization = db.organizations().insert();
+    userSession.addPermission(ADMINISTER_QUALITY_GATES, organization);
+    QGateWithOrgDto initialQualityGate = db.qualityGates().insertQualityGate(organization);
+    QGateWithOrgDto secondQualityGate = db.qualityGates().insertQualityGate(organization);
+    ComponentDto project = db.components().insertPrivateProject(organization);
+
+    ws.newRequest()
+      .setParam("gateId", initialQualityGate.getId().toString())
+      .setParam("projectKey", project.getKey())
+      .setParam("organization", organization.getKey())
+      .execute();
+
+    ws.newRequest()
+      .setParam("gateId", secondQualityGate.getId().toString())
+      .setParam("projectKey", project.getKey())
+      .setParam("organization", organization.getKey())
+      .execute();
+
+    assertSelected(secondQualityGate, project);
+  }
+
+  @Test
+  public void select_same_quality_gate_for_project_twice() {
+    OrganizationDto organization = db.organizations().insert();
+    userSession.addPermission(ADMINISTER_QUALITY_GATES, organization);
+    QGateWithOrgDto initialQualityGate = db.qualityGates().insertQualityGate(organization);
+    ComponentDto project = db.components().insertPrivateProject(organization);
+
+    ws.newRequest()
+      .setParam("gateId", initialQualityGate.getId().toString())
+      .setParam("projectKey", project.getKey())
+      .setParam("organization", organization.getKey())
+      .execute();
+
+    ws.newRequest()
+      .setParam("gateId", initialQualityGate.getId().toString())
+      .setParam("projectKey", project.getKey())
+      .setParam("organization", organization.getKey())
+      .execute();
+
+    assertSelected(initialQualityGate, project);
+  }
+
   @Test
   public void project_admin() {
     OrganizationDto organization = db.organizations().insert();
@@ -179,7 +225,7 @@ public class SelectActionTest {
     expectedException.expect(NotFoundException.class);
 
     ws.newRequest()
-      .setParam("gateId", String.valueOf("1"))
+      .setParam("gateId", "1")
       .setParam("projectKey", project.getKey())
       .setParam("organization", organization.getKey())
       .execute();
@@ -296,6 +342,11 @@ public class SelectActionTest {
 
   private void assertSelected(QualityGateDto qualityGate, ComponentDto project) {
     assertThat(dbClient.propertiesDao().selectProjectProperty(project.getId(), SONAR_QUALITYGATE_PROPERTY).getValue()).isEqualTo(qualityGate.getId().toString());
+    Optional<String> qGateUuid = db.qualityGates().selectQGateUuidByComponentUuid(project.uuid());
+    assertThat(qGateUuid)
+      .isNotNull()
+      .isNotEmpty()
+      .hasValue(qualityGate.getUuid());
   }
 
 }