Browse Source

SONAR-18872 improve total ncloc computation

tags/9.9.1.69595
Pierre 1 year ago
parent
commit
4114d4103e
23 changed files with 593 additions and 76 deletions
  1. 51
    0
      server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ProjectNclocComputationStep.java
  2. 1
    0
      server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java
  3. 65
    0
      server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ProjectNclocComputationStepIT.java
  4. 2
    8
      server/sonar-db-dao/src/main/java/org/sonar/db/measure/LiveMeasureDao.java
  5. 3
    5
      server/sonar-db-dao/src/main/java/org/sonar/db/measure/LiveMeasureMapper.java
  6. 13
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectDao.java
  7. 7
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectMapper.java
  8. 3
    8
      server/sonar-db-dao/src/main/resources/org/sonar/db/measure/LiveMeasureMapper.xml
  9. 15
    0
      server/sonar-db-dao/src/main/resources/org/sonar/db/project/ProjectMapper.xml
  10. 2
    1
      server/sonar-db-dao/src/schema/schema-sq.ddl
  11. 4
    36
      server/sonar-db-dao/src/test/java/org/sonar/db/measure/LiveMeasureDaoTest.java
  12. 26
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/project/ProjectDaoTest.java
  13. 56
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v99/AddNclocToProjects.java
  14. 4
    1
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v99/DbVersion99.java
  15. 56
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v99/PopulateNclocForProjects.java
  16. 54
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v99/AddNclocToProjectsTest.java
  17. 144
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v99/PopulateNclocForProjectsTest.java
  18. 15
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v99/AddNclocToProjectsTest/schema.sql
  19. 67
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v99/PopulateNclocForProjectsTest/schema.sql
  20. 1
    5
      server/sonar-webserver-core/src/main/java/org/sonar/server/platform/StatisticsSupport.java
  21. 1
    2
      server/sonar-webserver-core/src/test/java/org/sonar/server/platform/StatisticsSupportTest.java
  22. 1
    5
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/ui/ws/MarketplaceAction.java
  23. 2
    5
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/ui/ws/MarketplaceActionTest.java

+ 51
- 0
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ProjectNclocComputationStep.java View File

@@ -0,0 +1,51 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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.ce.task.projectanalysis.step;

import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.ce.task.step.ComputationStep;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;

public class ProjectNclocComputationStep implements ComputationStep {

private final AnalysisMetadataHolder analysisMetadataHolder;
private final DbClient dbClient;

public ProjectNclocComputationStep(AnalysisMetadataHolder analysisMetadataHolder, DbClient dbClient) {
this.analysisMetadataHolder = analysisMetadataHolder;
this.dbClient = dbClient;
}

@Override
public void execute(Context context) {
try (DbSession dbSession = dbClient.openSession(false)) {
String projectUuid = analysisMetadataHolder.getProject().getUuid();
long maxncloc = dbClient.liveMeasureDao().sumNclocOfBiggestBranchForProject(dbSession, projectUuid);
dbClient.projectDao().updateNcloc(dbSession, projectUuid, maxncloc);
dbSession.commit();
}
}

@Override
public String getDescription() {
return "Compute total Project ncloc";
}
}

+ 1
- 0
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java View File

@@ -110,6 +110,7 @@ public class ReportComputationSteps extends AbstractComputationSteps {
PurgeDatastoresStep.class,
IndexAnalysisStep.class,
UpdateNeedIssueSyncStep.class,
ProjectNclocComputationStep.class,
PersistPushEventsStep.class,

// notifications are sent at the end, so that webapp displays up-to-date information

+ 65
- 0
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ProjectNclocComputationStepIT.java View File

@@ -0,0 +1,65 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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.ce.task.projectanalysis.step;

import org.junit.Rule;
import org.junit.Test;
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
import org.sonar.ce.task.step.TestComputationStepContext;
import org.sonar.db.DbClient;
import org.sonar.db.DbTester;
import org.sonar.db.component.BranchType;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.metric.MetricDto;
import org.sonar.server.project.Project;

import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.api.measures.Metric.ValueType.INT;

public class ProjectNclocComputationStepIT {
@Rule
public DbTester db = DbTester.create();
private final DbClient dbClient = db.getDbClient();

@Rule
public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();

private final ProjectNclocComputationStep step = new ProjectNclocComputationStep(analysisMetadataHolder, dbClient);

@Test
public void test_computing_branch_ncloc() {
MetricDto ncloc = db.measures().insertMetric(m -> m.setKey("ncloc").setValueType(INT.toString()));
ComponentDto project = db.components().insertPublicProject();
ComponentDto branch1 = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH));
db.measures().insertLiveMeasure(branch1, ncloc, m -> m.setValue(200d));
ComponentDto branch2 = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH));
db.measures().insertLiveMeasure(branch2, ncloc, m -> m.setValue(10d));
analysisMetadataHolder.setProject(new Project(project.uuid(), project.getKey(), project.name(), project.description(), emptyList()));
step.execute(TestComputationStepContext.TestStatistics::new);

assertThat(dbClient.projectDao().getNclocSum(db.getSession())).isEqualTo(200L);
}

@Test
public void description_is_not_missing() {
assertThat(step.getDescription()).isNotBlank();
}
}

+ 2
- 8
server/sonar-db-dao/src/main/java/org/sonar/db/measure/LiveMeasureDao.java View File

@@ -94,14 +94,8 @@ public class LiveMeasureDao implements Dao {
mapper(dbSession).selectTreeByQuery(query, baseComponent.uuid(), query.getUuidPath(baseComponent), resultHandler);
}

/**
* Example:
* If Main Branch = 0 LOCs (provisioned but never analyzed) and the "largest branch" is 120 LOCs, I'm expecting to consider the value 120.
* If Main Branch = 100 LOCs and the "largest branch" is 120 LOCs, I'm expecting to consider the value 120.
* If Main Branch = 100 LOCs and the "largest branch" is 80 LOCs, I'm expecting to consider the value 100.
*/
public long sumNclocOfBiggestBranch(DbSession dbSession, SumNclocDbQuery dbQuery) {
Long ncloc = mapper(dbSession).sumNclocOfBiggestBranch(NCLOC_KEY, dbQuery.getOnlyPrivateProjects(), dbQuery.getProjectUuidToExclude());
public long sumNclocOfBiggestBranchForProject(DbSession dbSession, String projectUuid){
Long ncloc = mapper(dbSession).sumNclocOfBiggestBranchForProject(projectUuid, NCLOC_KEY);
return ncloc == null ? 0L : ncloc;
}


+ 3
- 5
server/sonar-db-dao/src/main/java/org/sonar/db/measure/LiveMeasureMapper.java View File

@@ -21,7 +21,7 @@ package org.sonar.db.measure;

import java.util.Collection;
import java.util.List;
import javax.annotation.Nullable;
import javax.annotation.CheckForNull;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.session.ResultHandler;

@@ -57,10 +57,8 @@ public interface LiveMeasureMapper {
@Param("baseUuidPath") String baseUuidPath,
ResultHandler<LiveMeasureDto> resultHandler);

Long sumNclocOfBiggestBranch(
@Param("ncloc") String nclocKey,
@Param("private") Boolean privateProject,
@Nullable @Param("projectUuidToExclude") String projectUuidToExclude);
@CheckForNull
Long sumNclocOfBiggestBranchForProject(@Param("projectUuid") String projectUuid, @Param("ncloc") String nclocKey);

List<LargestBranchNclocDto> getLargestBranchNclocPerProject();


+ 13
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectDao.java View File

@@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
import org.sonar.api.utils.System2;
import org.sonar.db.Dao;
import org.sonar.db.DbSession;
@@ -126,4 +127,16 @@ public class ProjectDao implements Dao {
Set<String> languageFilters = Set.of(language + "=%", "%;" + language + "=%");
return mapper(session).selectProjectUuidsAssociatedToDefaultQualityProfileByLanguage(languageFilters);
}

public void updateNcloc(DbSession dbSession, String projectUuid, long ncloc) {
mapper(dbSession).updateNcloc(projectUuid, ncloc);
}

public long getNclocSum(DbSession dbSession) {
return getNclocSum(dbSession, null);
}

public long getNclocSum(DbSession dbSession, @Nullable String projectUuidToExclude) {
return Optional.ofNullable(mapper(dbSession).getNclocSum(projectUuidToExclude)).orElse(0L);
}
}

+ 7
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectMapper.java View File

@@ -23,6 +23,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.ibatis.annotations.Param;

public interface ProjectMapper {
@@ -62,4 +63,10 @@ public interface ProjectMapper {
List<String> selectAllProjectUuids();

Set<String> selectProjectUuidsAssociatedToDefaultQualityProfileByLanguage(@Param("languageFilters") Set<String> languageFilters);

void updateNcloc(@Param("projectUuid") String projectUuid, @Param("ncloc") long ncloc);

@CheckForNull
Long getNclocSum(@Nullable @Param("projectUuidToExclude") String projectUuidToExclude);

}

+ 3
- 8
server/sonar-db-dao/src/main/resources/org/sonar/db/measure/LiveMeasureMapper.xml View File

@@ -61,8 +61,8 @@
and lm.component_uuid = #{componentUuid, jdbcType=VARCHAR}
</select>

<select id="sumNclocOfBiggestBranch" parameterType="map" resultType="long">
select sum(sumncloc.maxncloc) from (
<select id="sumNclocOfBiggestBranchForProject" parameterType="map" resultType="long">
select sumncloc.maxncloc from (
select b.project_uuid as projectUuid, max(lm.value) as maxncloc
from live_measures lm
inner join metrics m on m.uuid = lm.metric_uuid
@@ -70,12 +70,7 @@
inner join projects p on p.uuid = b.project_uuid and p.qualifier = 'TRK'
<where>
m.name = #{ncloc, jdbcType=VARCHAR}
<if test="private">
and p.private=${_true}
</if>
<if test="projectUuidToExclude != null">
and b.project_uuid &lt;&gt; #{projectUuidToExclude,jdbcType=VARCHAR}
</if>
and b.project_uuid = #{projectUuid,jdbcType=VARCHAR}
</where>
group by b.project_uuid
) sumncloc

+ 15
- 0
server/sonar-db-dao/src/main/resources/org/sonar/db/project/ProjectMapper.xml View File

@@ -179,4 +179,19 @@
uuid = #{uuid,jdbcType=VARCHAR}
</update>


<update id="updateNcloc">
update projects set
ncloc = #{ncloc,jdbcType=BIGINT}
where
uuid = #{projectUuid,jdbcType=VARCHAR}
</update>

<select id="getNclocSum" parameterType="string" resultType="long">
select sum(ncloc) from projects where qualifier = 'TRK'
<if test="projectUuidToExclude != null">
and uuid &lt;&gt; #{projectUuidToExclude,jdbcType=VARCHAR}
</if>
</select>

</mapper>

+ 2
- 1
server/sonar-db-dao/src/schema/schema-sq.ddl View File

@@ -723,7 +723,8 @@ CREATE TABLE "PROJECTS"(
"PRIVATE" BOOLEAN NOT NULL,
"TAGS" CHARACTER VARYING(500),
"CREATED_AT" BIGINT,
"UPDATED_AT" BIGINT NOT NULL
"UPDATED_AT" BIGINT NOT NULL,
"NCLOC" BIGINT
);
ALTER TABLE "PROJECTS" ADD CONSTRAINT "PK_NEW_PROJECTS" PRIMARY KEY("UUID");
CREATE UNIQUE INDEX "UNIQ_PROJECTS_KEE" ON "PROJECTS"("KEE" NULLS FIRST);

+ 4
- 36
server/sonar-db-dao/src/test/java/org/sonar/db/measure/LiveMeasureDaoTest.java View File

@@ -365,12 +365,9 @@ public class LiveMeasureDaoTest {
db.measures().insertLiveMeasure(projectWithLinesButNoLoc, lines, m -> m.setValue(365d));
db.measures().insertLiveMeasure(projectWithLinesButNoLoc, ncloc, m -> m.setValue(0d));

SumNclocDbQuery query = SumNclocDbQuery.builder()
.setOnlyPrivateProjects(false)
.build();
long result = underTest.sumNclocOfBiggestBranch(db.getSession(), query);

assertThat(result).isEqualTo(10L + 200L);
assertThat(underTest.sumNclocOfBiggestBranchForProject(db.getSession(), simpleProject.uuid())).isEqualTo(10L);
assertThat(underTest.sumNclocOfBiggestBranchForProject(db.getSession(), projectWithBiggerBranch.uuid())).isEqualTo(200L);
assertThat(underTest.sumNclocOfBiggestBranchForProject(db.getSession(), projectWithLinesButNoLoc.uuid())).isZero();
}

@Test
@@ -410,40 +407,11 @@ public class LiveMeasureDaoTest {
public void countNcloc_empty() {
db.measures().insertMetric(m -> m.setKey("ncloc").setValueType(INT.toString()));
db.measures().insertMetric(m -> m.setKey("lines").setValueType(INT.toString()));
SumNclocDbQuery query = SumNclocDbQuery.builder()
.setOnlyPrivateProjects(false)
.build();
long result = underTest.sumNclocOfBiggestBranch(db.getSession(), query);
long result = underTest.sumNclocOfBiggestBranchForProject(db.getSession(), "non-existing-project-uuid");

assertThat(result).isZero();
}

@Test
public void countNcloc_and_exclude_project() {
MetricDto ncloc = db.measures().insertMetric(m -> m.setKey("ncloc").setValueType(INT.toString()));

ComponentDto simpleProject = db.components().insertPublicProject();
db.measures().insertLiveMeasure(simpleProject, ncloc, m -> m.setValue(10d));

ComponentDto projectWithBiggerBranch = db.components().insertPublicProject();
ComponentDto bigBranch = db.components().insertProjectBranch(projectWithBiggerBranch, b -> b.setBranchType(BranchType.BRANCH));
db.measures().insertLiveMeasure(projectWithBiggerBranch, ncloc, m -> m.setValue(100d));
db.measures().insertLiveMeasure(bigBranch, ncloc, m -> m.setValue(200d));

ComponentDto projectToExclude = db.components().insertPublicProject();
ComponentDto projectToExcludeBranch = db.components().insertProjectBranch(projectToExclude, b -> b.setBranchType(BranchType.BRANCH));
db.measures().insertLiveMeasure(projectToExclude, ncloc, m -> m.setValue(300d));
db.measures().insertLiveMeasure(projectToExcludeBranch, ncloc, m -> m.setValue(400d));

SumNclocDbQuery query = SumNclocDbQuery.builder()
.setProjectUuidToExclude(projectToExclude.uuid())
.setOnlyPrivateProjects(false)
.build();
long result = underTest.sumNclocOfBiggestBranch(db.getSession(), query);

assertThat(result).isEqualTo(10L + 200L);
}

@Test
public void insert_data() {
byte[] data = "text_value".getBytes(StandardCharsets.UTF_8);

+ 26
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/project/ProjectDaoTest.java View File

@@ -31,6 +31,7 @@ import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.annotation.Nullable;
import org.assertj.core.api.Assertions;
import org.assertj.core.groups.Tuple;
import org.junit.Rule;
import org.junit.Test;
@@ -279,6 +280,31 @@ public class ProjectDaoTest {
.containsExactlyInAnyOrderElementsOf(extractComponentUuids(projects));
}

@Test
public void update_ncloc_should_update_project() {
ComponentDto project = db.components().insertPublicProject();

projectDao.updateNcloc(db.getSession(), project.uuid(), 10L);

Assertions.assertThat(projectDao.getNclocSum(db.getSession())).isEqualTo(10L);
}

@Test
public void getNcloc_sum_compute_correctly_sum_of_projects() {
projectDao.updateNcloc(db.getSession(), db.components().insertPublicProject().uuid(), 1L);
projectDao.updateNcloc(db.getSession(), db.components().insertPublicProject().uuid(), 20L);
projectDao.updateNcloc(db.getSession(), db.components().insertPublicProject().uuid(), 100L);
Assertions.assertThat(projectDao.getNclocSum(db.getSession())).isEqualTo(121L);
}

@Test
public void getNcloc_sum_compute_correctly_sum_of_projects_while_excluding_project() {
projectDao.updateNcloc(db.getSession(), db.components().insertPublicProject().uuid(), 1L);
projectDao.updateNcloc(db.getSession(), db.components().insertPublicProject().uuid(), 20L);
ComponentDto project3 = db.components().insertPublicProject();
projectDao.updateNcloc(db.getSession(), project3.uuid(), 100L);
Assertions.assertThat(projectDao.getNclocSum(db.getSession(), project3.uuid())).isEqualTo(21L);
}
@Test
public void selectAllProjectUuids_shouldOnlyReturnProjectWithTRKQualifier() {
ComponentDto application = db.components().insertPrivateApplication();

+ 56
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v99/AddNclocToProjects.java View File

@@ -0,0 +1,56 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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.v99;

import java.sql.SQLException;
import org.sonar.db.Database;
import org.sonar.db.DatabaseUtils;
import org.sonar.server.platform.db.migration.def.BigIntegerColumnDef;
import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder;
import org.sonar.server.platform.db.migration.step.DdlChange;

public class AddNclocToProjects extends DdlChange {

public static final String PROJECT_TABLE_NAME = "projects";
public static final String NCLOC_COLUMN_NAME = "ncloc";

public AddNclocToProjects(Database db) {
super(db);
}

@Override
public void execute(Context context) throws SQLException {
if (checkIfColumnExists()) {
return;
}
BigIntegerColumnDef columnDef = BigIntegerColumnDef.newBigIntegerColumnDefBuilder().setColumnName(NCLOC_COLUMN_NAME).setIsNullable(true).build();
String request = new AddColumnsBuilder(getDialect(), PROJECT_TABLE_NAME).addColumn(columnDef).build();
context.execute(request);
}

public boolean checkIfColumnExists() throws SQLException {
try (var connection = getDatabase().getDataSource().getConnection()) {
if (DatabaseUtils.tableColumnExists(connection, PROJECT_TABLE_NAME, NCLOC_COLUMN_NAME)) {
return true;
}
}
return false;
}
}

+ 4
- 1
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v99/DbVersion99.java View File

@@ -28,6 +28,9 @@ public class DbVersion99 implements DbVersion {
registry
.add(6800, "Add node_name column to ce_activity table", AddNodeNameColumnToCeActivityTable.class)
.add(6801, "Delete all analysis cache", DeleteAnalysisCache.class)
.add(6802, "Change user_uuid field size to 255 for audit table", UpdateUserUuidColumnSizeInAuditTable.class);
.add(6802, "Change user_uuid field size to 255 for audit table", UpdateUserUuidColumnSizeInAuditTable.class)
.add(6803, "Add ncloc to 'Projects' table", AddNclocToProjects.class)
.add(6804, "Populate ncloc in 'Projects' table", PopulateNclocForProjects.class)
;
}
}

+ 56
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v99/PopulateNclocForProjects.java View File

@@ -0,0 +1,56 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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.v99;

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 PopulateNclocForProjects extends DataChange {

private static final String SELECT_QUERY = """
SELECT b.project_uuid AS projectUuid, max(lm.value) AS maxncloc
FROM live_measures lm
INNER JOIN metrics m ON m.uuid = lm.metric_uuid
INNER JOIN project_branches b ON b.uuid = lm.component_uuid
INNER JOIN projects p on p.uuid = b.project_uuid and p.qualifier = 'TRK'
WHERE m.name = 'ncloc'
GROUP BY b.project_uuid
""";

public PopulateNclocForProjects(Database db) {
super(db);
}

@Override
protected void execute(Context context) throws SQLException {
MassUpdate massUpdate = context.prepareMassUpdate();
massUpdate.select(SELECT_QUERY);
massUpdate.update("update projects set ncloc = ? where uuid = ?");
massUpdate.execute((row, update) -> {
String uuid = row.getString(1);
Long ncloc = row.getLong(2);
update.setLong(1, ncloc);
update.setString(2, uuid);
return true;
});
}
}

+ 54
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v99/AddNclocToProjectsTest.java View File

@@ -0,0 +1,54 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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.v99;

import java.sql.SQLException;
import java.sql.Types;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.db.CoreDbTester;
import org.sonar.server.platform.db.migration.step.DdlChange;

public class AddNclocToProjectsTest {

private static final String TABLE_NAME = "projects";
private static final String COLUMN_NAME = "ncloc";

@Rule
public final CoreDbTester db = CoreDbTester.createForSchema(AddNclocToProjectsTest.class, "schema.sql");
private final DdlChange underTest = new AddNclocToProjects(db.database());

@Test
public void add_column() throws SQLException {
db.assertColumnDoesNotExist(TABLE_NAME, COLUMN_NAME);
db.assertColumnDoesNotExist(TABLE_NAME, COLUMN_NAME);
underTest.execute();
db.assertColumnDefinition(TABLE_NAME, COLUMN_NAME, Types.BIGINT, null, true);
}

@Test
public void migration_is_reentrant() throws SQLException {
db.assertColumnDoesNotExist(TABLE_NAME, COLUMN_NAME);
underTest.execute();
underTest.execute();
db.assertColumnDefinition(TABLE_NAME, COLUMN_NAME, Types.BIGINT, null, true);
}

}

+ 144
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v99/PopulateNclocForProjectsTest.java View File

@@ -0,0 +1,144 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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.v99;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.core.util.UuidFactory;
import org.sonar.core.util.UuidFactoryFast;
import org.sonar.db.CoreDbTester;
import org.sonar.server.platform.db.migration.step.DataChange;

import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;

public class PopulateNclocForProjectsTest {

private final UuidFactory uuidFactory = UuidFactoryFast.getInstance();

@Rule
public CoreDbTester db = CoreDbTester.createForSchema(PopulateNclocForProjectsTest.class, "schema.sql");

private final DataChange underTest = new PopulateNclocForProjects(db.database());

@Test
public void migration_populates_ncloc_for_projects() throws SQLException {
Map<String, Long> expectedNclocByProjectUuid = populateData();
underTest.execute();
verifyNclocCorrectlyPopulatedForProjects(expectedNclocByProjectUuid);
}

@Test
public void migration_should_be_reentrant() throws SQLException {
Map<String, Long> expectedNclocByProjectUuid = populateData();
underTest.execute();
// re-entrant
underTest.execute();
verifyNclocCorrectlyPopulatedForProjects(expectedNclocByProjectUuid);
}

private Map<String, Long> populateData() {
String nclocMetricUuid = insertMetric("ncloc");

String projectUuid1 = insertProject();
String project1Branch1 = insertProjectBranch(projectUuid1);
String project1Branch2 = insertProjectBranch(projectUuid1);

long project1maxNcloc = 100;
insertLiveMeasure(nclocMetricUuid, projectUuid1, project1Branch1, 80L);
insertLiveMeasure(nclocMetricUuid, projectUuid1, project1Branch2, project1maxNcloc);

String otherMetricUuid = insertMetric("other");
insertLiveMeasure(otherMetricUuid, projectUuid1, project1Branch1, 5000L);
insertLiveMeasure(otherMetricUuid, projectUuid1, project1Branch2, 6000L);

String projectUuid2 = insertProject();
String project2Branch1 = insertProjectBranch(projectUuid2);
String project2Branch2 = insertProjectBranch(projectUuid2);
String project2Branch3 = insertProjectBranch(projectUuid2);

long project2maxNcloc = 60;
insertLiveMeasure(nclocMetricUuid, projectUuid2, project2Branch1, 20L);
insertLiveMeasure(nclocMetricUuid, projectUuid2, project2Branch2, 50L);
insertLiveMeasure(nclocMetricUuid, projectUuid2, project2Branch3, project2maxNcloc);

return Map.of(projectUuid1, project1maxNcloc, projectUuid2, project2maxNcloc);
}

private void verifyNclocCorrectlyPopulatedForProjects(Map<String, Long> expectedNclocByProjectUuid) {
for (Map.Entry<String, Long> entry : expectedNclocByProjectUuid.entrySet()) {
String query = String.format("select ncloc from projects where uuid='%s'", entry.getKey());
Long nclocFromProject = (Long) db.selectFirst(query).get("NCLOC");
assertThat(nclocFromProject).isEqualTo(entry.getValue());
}
}

private String insertMetric(String name) {
Map<String, Object> map = new HashMap<>();
String uuid = uuidFactory.create();
map.put("UUID", uuid);
map.put("NAME", name);
db.executeInsert("metrics", map);
return uuid;
}

private String insertProject() {
Map<String, Object> map = new HashMap<>();
String uuid = uuidFactory.create();
map.put("UUID", uuid);
map.put("KEE", randomAlphabetic(20));
map.put("QUALIFIER", "TRK");
map.put("PRIVATE", true);
map.put("UPDATED_AT", System.currentTimeMillis());
db.executeInsert("projects", map);
return uuid;
}

private String insertProjectBranch(String projectUuid) {
Map<String, Object> map = new HashMap<>();
String uuid = uuidFactory.create();
map.put("UUID", uuid);
map.put("PROJECT_UUID", projectUuid);
map.put("KEE", randomAlphabetic(20));
map.put("BRANCH_TYPE", "PULL_REQUEST");
map.put("UPDATED_AT", System.currentTimeMillis());
map.put("CREATED_AT", System.currentTimeMillis());
map.put("NEED_ISSUE_SYNC", false);
db.executeInsert("project_branches", map);
return uuid;
}

private void insertLiveMeasure(String metricUuid, String projectUuid, String componentUuid, Long value) {
Map<String, Object> map = new HashMap<>();
String uuid = uuidFactory.create();
map.put("UUID", uuid);
map.put("PROJECT_UUID", projectUuid);
map.put("COMPONENT_UUID", componentUuid);
map.put("METRIC_UUID", metricUuid);
map.put("VALUE", value);
map.put("UPDATED_AT", System.currentTimeMillis());
map.put("CREATED_AT", System.currentTimeMillis());
db.executeInsert("live_measures", map);
}

}

+ 15
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v99/AddNclocToProjectsTest/schema.sql View File

@@ -0,0 +1,15 @@

CREATE TABLE "PROJECTS"(
"UUID" CHARACTER VARYING(40) NOT NULL,
"KEE" CHARACTER VARYING(400) NOT NULL,
"QUALIFIER" CHARACTER VARYING(10) NOT NULL,
"NAME" CHARACTER VARYING(2000),
"DESCRIPTION" CHARACTER VARYING(2000),
"PRIVATE" BOOLEAN NOT NULL,
"TAGS" CHARACTER VARYING(500),
"CREATED_AT" BIGINT,
"UPDATED_AT" BIGINT NOT NULL
);
ALTER TABLE "PROJECTS" ADD CONSTRAINT "PK_NEW_PROJECTS" PRIMARY KEY("UUID");
CREATE UNIQUE INDEX "UNIQ_PROJECTS_KEE" ON "PROJECTS"("KEE" NULLS FIRST);
CREATE INDEX "IDX_QUALIFIER" ON "PROJECTS"("QUALIFIER" NULLS FIRST);

+ 67
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v99/PopulateNclocForProjectsTest/schema.sql View File

@@ -0,0 +1,67 @@
CREATE TABLE "PROJECTS"(
"UUID" CHARACTER VARYING(40) NOT NULL,
"KEE" CHARACTER VARYING(400) NOT NULL,
"QUALIFIER" CHARACTER VARYING(10) NOT NULL,
"NAME" CHARACTER VARYING(2000),
"DESCRIPTION" CHARACTER VARYING(2000),
"PRIVATE" BOOLEAN NOT NULL,
"TAGS" CHARACTER VARYING(500),
"CREATED_AT" BIGINT,
"UPDATED_AT" BIGINT NOT NULL,
"NCLOC" BIGINT
);
ALTER TABLE "PROJECTS" ADD CONSTRAINT "PK_NEW_PROJECTS" PRIMARY KEY("UUID");
CREATE UNIQUE INDEX "UNIQ_PROJECTS_KEE" ON "PROJECTS"("KEE" NULLS FIRST);
CREATE INDEX "IDX_QUALIFIER" ON "PROJECTS"("QUALIFIER" NULLS FIRST);

CREATE TABLE "PROJECT_BRANCHES"(
"UUID" CHARACTER VARYING(50) NOT NULL,
"PROJECT_UUID" CHARACTER VARYING(50) NOT NULL,
"KEE" CHARACTER VARYING(255) NOT NULL,
"BRANCH_TYPE" CHARACTER VARYING(12) NOT NULL,
"MERGE_BRANCH_UUID" CHARACTER VARYING(50),
"PULL_REQUEST_BINARY" BINARY LARGE OBJECT,
"MANUAL_BASELINE_ANALYSIS_UUID" CHARACTER VARYING(40),
"CREATED_AT" BIGINT NOT NULL,
"UPDATED_AT" BIGINT NOT NULL,
"EXCLUDE_FROM_PURGE" BOOLEAN DEFAULT FALSE NOT NULL,
"NEED_ISSUE_SYNC" BOOLEAN NOT NULL
);
ALTER TABLE "PROJECT_BRANCHES" ADD CONSTRAINT "PK_PROJECT_BRANCHES" PRIMARY KEY("UUID");
CREATE UNIQUE INDEX "UNIQ_PROJECT_BRANCHES" ON "PROJECT_BRANCHES"("BRANCH_TYPE" NULLS FIRST, "PROJECT_UUID" NULLS FIRST, "KEE" NULLS FIRST);

CREATE TABLE "LIVE_MEASURES"(
"UUID" CHARACTER VARYING(40) NOT NULL,
"PROJECT_UUID" CHARACTER VARYING(50) NOT NULL,
"COMPONENT_UUID" CHARACTER VARYING(50) NOT NULL,
"METRIC_UUID" CHARACTER VARYING(40) NOT NULL,
"VALUE" DOUBLE PRECISION,
"TEXT_VALUE" CHARACTER VARYING(4000),
"MEASURE_DATA" BINARY LARGE OBJECT,
"UPDATE_MARKER" CHARACTER VARYING(40),
"CREATED_AT" BIGINT NOT NULL,
"UPDATED_AT" BIGINT NOT NULL
);
ALTER TABLE "LIVE_MEASURES" ADD CONSTRAINT "PK_LIVE_MEASURES" PRIMARY KEY("UUID");
CREATE INDEX "LIVE_MEASURES_PROJECT" ON "LIVE_MEASURES"("PROJECT_UUID" NULLS FIRST);
CREATE UNIQUE INDEX "LIVE_MEASURES_COMPONENT" ON "LIVE_MEASURES"("COMPONENT_UUID" NULLS FIRST, "METRIC_UUID" NULLS FIRST);

CREATE TABLE "METRICS"(
"UUID" CHARACTER VARYING(40) NOT NULL,
"NAME" CHARACTER VARYING(64) NOT NULL,
"DESCRIPTION" CHARACTER VARYING(255),
"DIRECTION" INTEGER DEFAULT 0 NOT NULL,
"DOMAIN" CHARACTER VARYING(64),
"SHORT_NAME" CHARACTER VARYING(64),
"QUALITATIVE" BOOLEAN DEFAULT FALSE NOT NULL,
"VAL_TYPE" CHARACTER VARYING(8),
"ENABLED" BOOLEAN DEFAULT TRUE,
"WORST_VALUE" DOUBLE PRECISION,
"BEST_VALUE" DOUBLE PRECISION,
"OPTIMIZED_BEST_VALUE" BOOLEAN,
"HIDDEN" BOOLEAN,
"DELETE_HISTORICAL_DATA" BOOLEAN,
"DECIMAL_SCALE" INTEGER
);
ALTER TABLE "METRICS" ADD CONSTRAINT "PK_METRICS" PRIMARY KEY("UUID");
CREATE UNIQUE INDEX "METRICS_UNIQUE_NAME" ON "METRICS"("NAME" NULLS FIRST);

+ 1
- 5
server/sonar-webserver-core/src/main/java/org/sonar/server/platform/StatisticsSupport.java View File

@@ -21,7 +21,6 @@ package org.sonar.server.platform;

import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.measure.SumNclocDbQuery;

public class StatisticsSupport {

@@ -33,10 +32,7 @@ public class StatisticsSupport {

public long getLinesOfCode(){
try (DbSession dbSession = dbClient.openSession(false)) {
SumNclocDbQuery query = SumNclocDbQuery.builder()
.setOnlyPrivateProjects(false)
.build();
return dbClient.liveMeasureDao().sumNclocOfBiggestBranch(dbSession, query);
return dbClient.projectDao().getNclocSum(dbSession);
}
}
}

+ 1
- 2
server/sonar-webserver-core/src/test/java/org/sonar/server/platform/StatisticsSupportTest.java View File

@@ -22,7 +22,6 @@ package org.sonar.server.platform;
import org.junit.Test;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.measure.SumNclocDbQuery;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -37,7 +36,7 @@ public class StatisticsSupportTest {

@Test
public void should_return_metric_from_liveMeasureDao() {
when(dbClient.liveMeasureDao().sumNclocOfBiggestBranch(any(DbSession.class), any(SumNclocDbQuery.class))).thenReturn(1800999L);
when(dbClient.projectDao().getNclocSum(any(DbSession.class))).thenReturn(1800999L);

long linesOfCode = statisticsSupport.getLinesOfCode();


+ 1
- 5
server/sonar-webserver-webapi/src/main/java/org/sonar/server/ui/ws/MarketplaceAction.java View File

@@ -25,7 +25,6 @@ import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.measure.SumNclocDbQuery;
import org.sonar.server.user.UserSession;
import org.sonarqube.ws.Navigation;

@@ -68,10 +67,7 @@ public class MarketplaceAction implements NavigationWsAction {

private long computeNcloc() {
try (DbSession dbSession = dbClient.openSession(false)) {
SumNclocDbQuery query = SumNclocDbQuery.builder()
.setOnlyPrivateProjects(false)
.build();
return dbClient.liveMeasureDao().sumNclocOfBiggestBranch(dbSession, query);
return dbClient.projectDao().getNclocSum(dbSession);
}
}
}

+ 2
- 5
server/sonar-webserver-webapi/src/test/java/org/sonar/server/ui/ws/MarketplaceActionTest.java View File

@@ -28,7 +28,6 @@ import org.sonar.api.server.ws.WebService;
import org.sonar.db.DbClient;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.metric.MetricDto;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.tester.UserSessionRule;
@@ -40,8 +39,6 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
import static org.sonar.api.measures.Metric.ValueType.INT;
import static org.sonar.test.JsonAssert.assertJson;

@RunWith(DataProviderRunner.class)
@@ -120,7 +117,7 @@ public class MarketplaceActionTest {

private void setNcloc(double ncloc) {
ComponentDto project = db.components().insertPublicProject();
MetricDto nclocMetric = db.measures().insertMetric(m -> m.setValueType(INT.toString()).setKey(NCLOC_KEY));
db.measures().insertLiveMeasure(project, nclocMetric, m -> m.setValue(ncloc));
db.getDbClient().projectDao().updateNcloc(db.getSession(), project.uuid(), (long) ncloc);
db.commit();
}
}

Loading…
Cancel
Save