]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19084 Add telemetry information (managed instance provider)
authorAurelien Poscia <aurelien.poscia@sonarsource.com>
Fri, 5 May 2023 14:45:36 +0000 (16:45 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 11 May 2023 20:03:14 +0000 (20:03 +0000)
server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java
server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java
server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java
server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java
server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java

index 1682ef8ab8f93a046fcc794d030a4ec1e222a52e..c36e91ea3332d31393c52e00900d119e33f71cb2 100644 (file)
@@ -43,7 +43,7 @@ public class TelemetryData {
   private final Long installationDate;
   private final String installationVersion;
   private final boolean inDocker;
-  private final boolean isScimEnabled;
+  private final ManagedInstanceInformation managedInstanceInformation;
   private final List<UserTelemetryDto> users;
   private final List<Project> projects;
   private final List<ProjectStatistics> projectStatistics;
@@ -63,7 +63,6 @@ public class TelemetryData {
     installationDate = builder.installationDate;
     installationVersion = builder.installationVersion;
     inDocker = builder.inDocker;
-    isScimEnabled = builder.isScimEnabled;
     users = builder.users;
     projects = builder.projects;
     projectStatistics = builder.projectStatistics;
@@ -71,6 +70,7 @@ public class TelemetryData {
     hasUnanalyzedC = builder.hasUnanalyzedC;
     hasUnanalyzedCpp = builder.hasUnanalyzedCpp;
     customSecurityConfigs = requireNonNullElse(builder.customSecurityConfigs, Set.of());
+    managedInstanceInformation = builder.managedInstanceInformation;
   }
 
   public String getServerId() {
@@ -113,8 +113,8 @@ public class TelemetryData {
     return inDocker;
   }
 
-  public boolean isScimEnabled() {
-    return isScimEnabled;
+  public ManagedInstanceInformation getManagedInstanceInformation() {
+    return managedInstanceInformation;
   }
 
   public Optional<Boolean> hasUnanalyzedC() {
@@ -160,7 +160,7 @@ public class TelemetryData {
     private Long installationDate;
     private String installationVersion;
     private boolean inDocker = false;
-    private boolean isScimEnabled;
+    private ManagedInstanceInformation managedInstanceInformation;
     private Boolean hasUnanalyzedC;
     private Boolean hasUnanalyzedCpp;
     private Set<String> customSecurityConfigs;
@@ -248,8 +248,8 @@ public class TelemetryData {
       return this;
     }
 
-    public Builder setIsScimEnabled(boolean isEnabled) {
-      this.isScimEnabled = isEnabled;
+    Builder setManagedInstanceInformation(ManagedInstanceInformation managedInstanceInformation) {
+      this.managedInstanceInformation = managedInstanceInformation;
       return this;
     }
 
@@ -283,6 +283,9 @@ public class TelemetryData {
   record QualityGate(String uuid, String caycStatus) {
   }
 
+  record ManagedInstanceInformation(boolean isManaged, @Nullable String provider) {
+  }
+
   public static class ProjectStatistics {
     private final String projectUuid;
     private final Long branchCount;
index 350cbb4943fcf63ecf5a2301dcb2e483594b911f..b4a5eafbe09df42fe66f483a601cdd17614a2d67 100644 (file)
@@ -36,9 +36,10 @@ import static org.sonar.api.utils.DateUtils.DATETIME_FORMAT;
 public class TelemetryDataJsonWriter {
 
   @VisibleForTesting
-  static final String SCIM_PROPERTY = "scim";
+  static final String MANAGED_INSTANCE_PROPERTY = "managedInstanceInformation";
 
   private static final String LANGUAGE_PROPERTY = "language";
+  private static final String VERSION = "version";
 
   private final List<TelemetryExtension> extensions;
 
@@ -49,64 +50,62 @@ public class TelemetryDataJsonWriter {
     this.system2 = system2;
   }
 
-  public void writeTelemetryData(JsonWriter json, TelemetryData statistics) {
+  public void writeTelemetryData(JsonWriter json, TelemetryData telemetryData) {
     json.beginObject();
-    json.prop("id", statistics.getServerId());
-    json.prop("version", statistics.getVersion());
-    json.prop("messageSequenceNumber", statistics.getMessageSequenceNumber());
+    json.prop("id", telemetryData.getServerId());
+    json.prop(VERSION, telemetryData.getVersion());
+    json.prop("messageSequenceNumber", telemetryData.getMessageSequenceNumber());
     json.prop("localTimestamp", toUtc(system2.now()));
-    statistics.getEdition().ifPresent(e -> json.prop("edition", e.name().toLowerCase(Locale.ENGLISH)));
-    json.prop("defaultQualityGate", statistics.getDefaultQualityGate());
+    telemetryData.getEdition().ifPresent(e -> json.prop("edition", e.name().toLowerCase(Locale.ENGLISH)));
+    json.prop("defaultQualityGate", telemetryData.getDefaultQualityGate());
     json.name("database");
     json.beginObject();
-    json.prop("name", statistics.getDatabase().name());
-    json.prop("version", statistics.getDatabase().version());
+    json.prop("name", telemetryData.getDatabase().name());
+    json.prop(VERSION, telemetryData.getDatabase().version());
     json.endObject();
     json.name("plugins");
     json.beginArray();
-    statistics.getPlugins().forEach((plugin, version) -> {
+    telemetryData.getPlugins().forEach((plugin, version) -> {
       json.beginObject();
       json.prop("name", plugin);
-      json.prop("version", version);
+      json.prop(VERSION, version);
       json.endObject();
     });
     json.endArray();
 
-    if (!statistics.getCustomSecurityConfigs().isEmpty()) {
+    if (!telemetryData.getCustomSecurityConfigs().isEmpty()) {
       json.name("customSecurityConfig");
       json.beginArray();
-      json.values(statistics.getCustomSecurityConfigs());
+      json.values(telemetryData.getCustomSecurityConfigs());
       json.endArray();
     }
 
-    statistics.hasUnanalyzedC().ifPresent(hasUnanalyzedC -> json.prop("hasUnanalyzedC", hasUnanalyzedC));
-    statistics.hasUnanalyzedCpp().ifPresent(hasUnanalyzedCpp -> json.prop("hasUnanalyzedCpp", hasUnanalyzedCpp));
+    telemetryData.hasUnanalyzedC().ifPresent(hasUnanalyzedC -> json.prop("hasUnanalyzedC", hasUnanalyzedC));
+    telemetryData.hasUnanalyzedCpp().ifPresent(hasUnanalyzedCpp -> json.prop("hasUnanalyzedCpp", hasUnanalyzedCpp));
 
-    if (statistics.getInstallationDate() != null) {
-      json.prop("installationDate", toUtc(statistics.getInstallationDate()));
+    if (telemetryData.getInstallationDate() != null) {
+      json.prop("installationDate", toUtc(telemetryData.getInstallationDate()));
     }
-    if (statistics.getInstallationVersion() != null) {
-      json.prop("installationVersion", statistics.getInstallationVersion());
+    if (telemetryData.getInstallationVersion() != null) {
+      json.prop("installationVersion", telemetryData.getInstallationVersion());
     }
-    json.prop("docker", statistics.isInDocker());
-
-    json.prop(SCIM_PROPERTY, statistics.isScimEnabled());
-
-    writeUserData(json, statistics);
-    writeProjectData(json, statistics);
-    writeProjectStatsData(json, statistics);
-    writeQualityGates(json, statistics);
+    json.prop("docker", telemetryData.isInDocker());
 
+    writeUserData(json, telemetryData);
+    writeProjectData(json, telemetryData);
+    writeProjectStatsData(json, telemetryData);
+    writeQualityGates(json, telemetryData);
+    writeManagedInstanceInformation(json, telemetryData.getManagedInstanceInformation());
     extensions.forEach(e -> e.write(json));
 
     json.endObject();
   }
 
-  private static void writeUserData(JsonWriter json, TelemetryData statistics) {
-    if (statistics.getUserTelemetries() != null) {
+  private static void writeUserData(JsonWriter json, TelemetryData telemetryData) {
+    if (telemetryData.getUserTelemetries() != null) {
       json.name("users");
       json.beginArray();
-      statistics.getUserTelemetries().forEach(user -> {
+      telemetryData.getUserTelemetries().forEach(user -> {
         json.beginObject();
         json.prop("userUuid", DigestUtils.sha3_224Hex(user.getUuid()));
         json.prop("status", user.isActive() ? "active" : "inactive");
@@ -126,11 +125,11 @@ public class TelemetryDataJsonWriter {
     }
   }
 
-  private static void writeProjectData(JsonWriter json, TelemetryData statistics) {
-    if (statistics.getProjects() != null) {
+  private static void writeProjectData(JsonWriter json, TelemetryData telemetryData) {
+    if (telemetryData.getProjects() != null) {
       json.name("projects");
       json.beginArray();
-      statistics.getProjects().forEach(project -> {
+      telemetryData.getProjects().forEach(project -> {
         json.beginObject();
         json.prop("projectUuid", project.projectUuid());
         if (project.lastAnalysis() != null) {
@@ -144,11 +143,11 @@ public class TelemetryDataJsonWriter {
     }
   }
 
-  private static void writeProjectStatsData(JsonWriter json, TelemetryData statistics) {
-    if (statistics.getProjectStatistics() != null) {
+  private static void writeProjectStatsData(JsonWriter json, TelemetryData telemetryData) {
+    if (telemetryData.getProjectStatistics() != null) {
       json.name("projects-general-stats");
       json.beginArray();
-      statistics.getProjectStatistics().forEach(project -> {
+      telemetryData.getProjectStatistics().forEach(project -> {
         json.beginObject();
         json.prop("projectUuid", project.getProjectUuid());
         json.prop("branchCount", project.getBranchCount());
@@ -168,11 +167,11 @@ public class TelemetryDataJsonWriter {
     }
   }
 
-  private static void writeQualityGates(JsonWriter json, TelemetryData statistics) {
-    if (statistics.getQualityGates() != null) {
+  private static void writeQualityGates(JsonWriter json, TelemetryData telemetryData) {
+    if (telemetryData.getQualityGates() != null) {
       json.name("quality-gates");
       json.beginArray();
-      statistics.getQualityGates().forEach(qualityGate -> {
+      telemetryData.getQualityGates().forEach(qualityGate -> {
         json.beginObject();
         json.prop("uuid", qualityGate.uuid());
         json.prop("caycStatus", qualityGate.caycStatus());
@@ -182,6 +181,14 @@ public class TelemetryDataJsonWriter {
     }
   }
 
+  private static void writeManagedInstanceInformation(JsonWriter json, TelemetryData.ManagedInstanceInformation provider) {
+    json.name(MANAGED_INSTANCE_PROPERTY);
+    json.beginObject();
+    json.prop("isManaged", provider.isManaged());
+    json.prop("provider", provider.isManaged() ? provider.provider() : null);
+    json.endObject();
+  }
+
   @NotNull
   private static String toUtc(long date) {
     return DateTimeFormatter.ofPattern(DATETIME_FORMAT)
index 647cac10aec9670988a52a47d141c62ae9e51c61..3089a4da071dc03c8e6e3061c45bceba0d3898a3 100644 (file)
@@ -43,13 +43,11 @@ import org.sonar.core.telemetry.TelemetryExtension;
 import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.db.user.UserTelemetryDto;
 
-import static java.lang.String.format;
 import static java.util.stream.Collectors.joining;
 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
-import static org.sonar.server.telemetry.TelemetryDataJsonWriter.SCIM_PROPERTY;
 import static org.sonar.test.JsonAssert.assertJson;
 
 @RunWith(DataProviderRunner.class)
@@ -233,16 +231,43 @@ public class TelemetryDataJsonWriterTest {
       """.formatted(isInDocker));
   }
 
+  @DataProvider
+  public static Object[][] getManagedInstanceData() {
+    return new Object[][] {
+      {true, "scim"},
+      {true, "github"},
+      {true, "gitlab"},
+      {false, null},
+    };
+  }
+
   @Test
-  @UseDataProvider("getFeatureFlagEnabledStates")
-  public void write_scim_feature_flag(boolean isScimEnabled) {
+  @UseDataProvider("getManagedInstanceData")
+  public void writeTelemetryData_encodesCorrectlyManagedInstanceInformation(boolean isManaged, String provider) {
     TelemetryData data = telemetryBuilder()
-      .setIsScimEnabled(isScimEnabled)
+      .setManagedInstanceInformation(new TelemetryData.ManagedInstanceInformation(isManaged, provider))
       .build();
 
     String json = writeTelemetryData(data);
 
-    assertJson(json).isSimilarTo("{" + format("  \"%s\":", SCIM_PROPERTY) + isScimEnabled + "}");
+    if (isManaged) {
+      assertJson(json).isSimilarTo("""
+        {
+        "managedInstanceInformation": {
+          "isManaged": true,
+            "provider": "%s"
+          }
+        }
+        """.formatted(provider));
+    } else {
+      assertJson(json).isSimilarTo("""
+        {
+        "managedInstanceInformation": {
+          "isManaged": false
+          }
+        }
+        """);
+    }
   }
 
   @Test
@@ -478,6 +503,7 @@ public class TelemetryDataJsonWriterTest {
       .setVersion("bar")
       .setMessageSequenceNumber(1L)
       .setPlugins(Collections.emptyMap())
+      .setManagedInstanceInformation(new TelemetryData.ManagedInstanceInformation(false, null))
       .setDatabase(new TelemetryData.Database("H2", "11"));
   }
 
@@ -530,7 +556,7 @@ public class TelemetryDataJsonWriterTest {
   @DataProvider
   public static Object[][] allEditions() {
     return Arrays.stream(EditionProvider.Edition.values())
-      .map(t -> new Object[]{t})
+      .map(t -> new Object[] {t})
       .toArray(Object[][]::new);
   }
 
index bf8dad42226d78ef5e36f7a9e145b1d2a2c38987..0d87f2b9e0b32735295153627d0a4591dfca2721 100644 (file)
@@ -53,6 +53,7 @@ import org.sonar.db.measure.ProjectLocDistributionDto;
 import org.sonar.db.metric.MetricDto;
 import org.sonar.db.qualitygate.ProjectQgateAssociationDto;
 import org.sonar.db.qualitygate.QualityGateDto;
+import org.sonar.server.management.ManagedInstanceService;
 import org.sonar.server.platform.DockerSupport;
 import org.sonar.server.property.InternalProperties;
 import org.sonar.server.qualitygate.QualityGateCaycChecker;
@@ -101,11 +102,13 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
   private final DockerSupport dockerSupport;
   private final QualityGateCaycChecker qualityGateCaycChecker;
   private final QualityGateFinder qualityGateFinder;
+  private final ManagedInstanceService managedInstanceService;
 
   @Inject
   public TelemetryDataLoaderImpl(Server server, DbClient dbClient, PluginRepository pluginRepository,
     PlatformEditionProvider editionProvider, InternalProperties internalProperties, Configuration configuration,
-    DockerSupport dockerSupport, QualityGateCaycChecker qualityGateCaycChecker, QualityGateFinder qualityGateFinder) {
+    DockerSupport dockerSupport, QualityGateCaycChecker qualityGateCaycChecker, QualityGateFinder qualityGateFinder,
+    ManagedInstanceService managedInstanceService) {
     this.server = server;
     this.dbClient = dbClient;
     this.pluginRepository = pluginRepository;
@@ -115,6 +118,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
     this.dockerSupport = dockerSupport;
     this.qualityGateCaycChecker = qualityGateCaycChecker;
     this.qualityGateFinder = qualityGateFinder;
+    this.managedInstanceService = managedInstanceService;
   }
 
   private static Database loadDatabaseMetadata(DbSession dbSession) {
@@ -157,13 +161,15 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
     installationDateProperty.ifPresent(s -> data.setInstallationDate(Long.valueOf(s)));
     Optional<String> installationVersionProperty = internalProperties.read(InternalProperties.INSTALLATION_VERSION);
 
+
     return data
       .setInstallationVersion(installationVersionProperty.orElse(null))
       .setInDocker(dockerSupport.isRunningInDocker())
-      .setIsScimEnabled(isScimEnabled())
+      .setManagedInstanceInformation(buildManagedInstanceInformation())
       .build();
   }
 
+
   private void resolveUnanalyzedLanguageCode(TelemetryData.Builder data, DbSession dbSession) {
     long numberOfUnanalyzedCMeasures = dbClient.liveMeasureDao().countProjectsHavingMeasure(dbSession, UNANALYZED_C_KEY);
     long numberOfUnanalyzedCppMeasures = dbClient.liveMeasureDao().countProjectsHavingMeasure(dbSession, UNANALYZED_CPP_KEY);
@@ -358,4 +364,10 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
   private boolean isScimEnabled() {
     return this.internalProperties.read(SCIM_PROPERTY_ENABLED).map(Boolean::parseBoolean).orElse(false);
   }
+
+  private TelemetryData.ManagedInstanceInformation buildManagedInstanceInformation() {
+    String provider = managedInstanceService.isInstanceExternallyManaged() ? managedInstanceService.getProviderName() : null;
+    return new TelemetryData.ManagedInstanceInformation(managedInstanceService.isInstanceExternallyManaged(), provider);
+  }
+
 }
index d220c826d201b94a1b9fecb593d25c56c2970528..2018cd1095cf08f9065b7aa06c86a3cef525a30c 100644 (file)
@@ -52,6 +52,7 @@ import org.sonar.db.qualitygate.QualityGateDto;
 import org.sonar.db.user.UserDbTester;
 import org.sonar.db.user.UserDto;
 import org.sonar.db.user.UserTelemetryDto;
+import org.sonar.server.management.ManagedInstanceService;
 import org.sonar.server.platform.DockerSupport;
 import org.sonar.server.property.InternalProperties;
 import org.sonar.server.property.MapInternalProperties;
@@ -86,7 +87,6 @@ import static org.sonar.db.component.BranchType.BRANCH;
 import static org.sonar.server.metric.UnanalyzedLanguageMetrics.UNANALYZED_CPP_KEY;
 import static org.sonar.server.metric.UnanalyzedLanguageMetrics.UNANALYZED_C_KEY;
 import static org.sonar.server.qualitygate.QualityGateCaycStatus.NON_COMPLIANT;
-import static org.sonar.server.telemetry.TelemetryDataLoaderImpl.SCIM_PROPERTY_ENABLED;
 
 @RunWith(DataProviderRunner.class)
 public class TelemetryDataLoaderImplTest {
@@ -104,11 +104,12 @@ public class TelemetryDataLoaderImplTest {
   private final QualityGateCaycChecker qualityGateCaycChecker = mock(QualityGateCaycChecker.class);
   private final QualityGateFinder qualityGateFinder = new QualityGateFinder(db.getDbClient());
   private final InternalProperties internalProperties = spy(new MapInternalProperties());
+  private final ManagedInstanceService managedInstanceService = mock(ManagedInstanceService.class);
 
   private final TelemetryDataLoader communityUnderTest = new TelemetryDataLoaderImpl(server, db.getDbClient(), pluginRepository, editionProvider,
-      internalProperties, configuration, dockerSupport, qualityGateCaycChecker, qualityGateFinder);
+      internalProperties, configuration, dockerSupport, qualityGateCaycChecker, qualityGateFinder, managedInstanceService);
   private final TelemetryDataLoader commercialUnderTest = new TelemetryDataLoaderImpl(server, db.getDbClient(), pluginRepository, editionProvider,
-      internalProperties, configuration, dockerSupport, qualityGateCaycChecker, qualityGateFinder);
+      internalProperties, configuration, dockerSupport, qualityGateCaycChecker, qualityGateFinder, managedInstanceService);
 
   private QualityGateDto builtInDefaultQualityGate;
   private MetricDto bugsDto;
@@ -440,14 +441,16 @@ public class TelemetryDataLoaderImplTest {
   }
 
   @Test
-  @UseDataProvider("getScimFeatureStatues")
-  public void detect_scim_feature_status(String isEnabled) {
-    db.components().insertPublicProject().getMainBranchComponent();
-    when(internalProperties.read(SCIM_PROPERTY_ENABLED)).thenReturn(Optional.ofNullable(isEnabled));
+  @UseDataProvider("getManagedInstanceData")
+  public void managedInstanceData_containsCorrectInformation(boolean isManaged, String provider) {
+    when(managedInstanceService.isInstanceExternallyManaged()).thenReturn(isManaged);
+    when(managedInstanceService.getProviderName()).thenReturn(provider);
 
-    TelemetryData data = communityUnderTest.load();
+    TelemetryData data = commercialUnderTest.load();
 
-    assertThat(data.isScimEnabled()).isEqualTo(Boolean.parseBoolean(isEnabled));
+    TelemetryData.ManagedInstanceInformation managedInstance = data.getManagedInstanceInformation();
+    assertThat(managedInstance.isManaged()).isEqualTo(isManaged);
+    assertThat(managedInstance.provider()).isEqualTo(provider);
   }
 
   @Test
@@ -483,4 +486,14 @@ public class TelemetryDataLoaderImplTest {
     result.add(null);
     return result;
   }
+
+  @DataProvider
+  public static Object[][] getManagedInstanceData() {
+    return new Object[][]{
+      {true, "scim"},
+      {true, "github"},
+      {true, "gitlab"},
+      {false, null},
+    };
+  }
 }