@@ -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; |
@@ -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) |
@@ -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); | |||
} | |||
@@ -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); | |||
} | |||
} |
@@ -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}, | |||
}; | |||
} | |||
} |