From f47bed3cff9c970cb30a5c2d43b1e05d890cb04b Mon Sep 17 00:00:00 2001 From: Aurelien Poscia Date: Fri, 5 May 2023 16:45:36 +0200 Subject: [PATCH] SONAR-19084 Add telemetry information (managed instance provider) --- .../sonar/server/telemetry/TelemetryData.java | 17 ++-- .../telemetry/TelemetryDataJsonWriter.java | 85 ++++++++++--------- .../TelemetryDataJsonWriterTest.java | 40 +++++++-- .../telemetry/TelemetryDataLoaderImpl.java | 16 +++- .../TelemetryDataLoaderImplTest.java | 31 +++++-- 5 files changed, 125 insertions(+), 64 deletions(-) diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java b/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java index 1682ef8ab8f..c36e91ea333 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java @@ -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 users; private final List projects; private final List 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 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 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; diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java b/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java index 350cbb4943f..b4a5eafbe09 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java @@ -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 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) diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java index 647cac10aec..3089a4da071 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java @@ -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); } diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java index bf8dad42226..0d87f2b9e0b 100644 --- a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java @@ -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 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); + } + } diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java index d220c826d20..2018cd1095c 100644 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java @@ -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}, + }; + } } -- 2.39.5