*/
package org.sonar.server.telemetry;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.core.platform.EditionProvider;
import org.sonar.core.platform.EditionProvider.Edition;
import org.sonar.db.user.UserTelemetryDto;
-import static java.util.Collections.emptyList;
-import static java.util.Objects.requireNonNull;
+import static java.util.Objects.requireNonNullElse;
public class TelemetryData {
private final String serverId;
private final Long installationDate;
private final String installationVersion;
private final boolean inDocker;
- private final List<String> customSecurityConfigs;
+ private final boolean isScimEnabled;
private final List<UserTelemetryDto> users;
private final List<Project> projects;
private final List<ProjectStatistics> projectStatistics;
+ private final Set<String> customSecurityConfigs;
private TelemetryData(Builder builder) {
serverId = builder.serverId;
installationDate = builder.installationDate;
installationVersion = builder.installationVersion;
inDocker = builder.inDocker;
- customSecurityConfigs = builder.customSecurityConfigs == null ? emptyList() : builder.customSecurityConfigs;
+ isScimEnabled = builder.isScimEnabled;
users = builder.users;
projects = builder.projects;
projectStatistics = builder.projectStatistics;
+ customSecurityConfigs = requireNonNullElse(builder.customSecurityConfigs, Set.of());
}
public String getServerId() {
return inDocker;
}
- public List<String> getCustomSecurityConfigs() {
+ public boolean isScimEnabled() {
+ return isScimEnabled;
+ }
+
+ public Set<String> getCustomSecurityConfigs() {
return customSecurityConfigs;
}
private Long installationDate;
private String installationVersion;
private boolean inDocker = false;
- private List<String> customSecurityConfigs;
+ private boolean isScimEnabled;
+ private Set<String> customSecurityConfigs;
private List<UserTelemetryDto> users;
private List<Project> projects;
private List<ProjectStatistics> projectStatistics;
return this;
}
- Builder setCustomSecurityConfigs(List<String> customSecurityConfigs) {
+ Builder setCustomSecurityConfigs(Set<String> customSecurityConfigs) {
this.customSecurityConfigs = customSecurityConfigs;
return this;
}
return this;
}
- TelemetryData build() {
- requireNonNull(serverId);
- requireNonNull(version);
- requireNonNull(plugins);
- requireNonNull(database);
+ public Builder setIsScimEnabled(boolean isEnabled) {
+ this.isScimEnabled = isEnabled;
+ return this;
+ }
+ TelemetryData build() {
+ requireNonNullValues(serverId, version, plugins, database);
return new TelemetryData(this);
}
this.projectStatistics = projectStatistics;
return this;
}
+
+ private static void requireNonNullValues(Object... values) {
+ Arrays.stream(values).forEach(Objects::requireNonNull);
+ }
}
static class Database {
*/
package org.sonar.server.telemetry;
+import com.google.common.annotations.VisibleForTesting;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
public class TelemetryDataJsonWriter {
- public static final String LANGUAGE_PROP = "language";
+ @VisibleForTesting
+ static final String SCIM_PROPERTY = "scim";
+ private static final String LANGUAGE_PROPERTY = "language";
public void writeTelemetryData(JsonWriter json, TelemetryData statistics) {
json.beginObject();
}
json.prop("docker", statistics.isInDocker());
+ json.prop(SCIM_PROPERTY, statistics.isScimEnabled());
+
writeUserData(json, statistics);
writeProjectData(json, statistics);
writeProjectStatsData(json, statistics);
if (project.getLastAnalysis() != null) {
json.prop("lastAnalysis", toUtc(project.getLastAnalysis()));
}
- json.prop(LANGUAGE_PROP, project.getLanguage());
+ json.prop(LANGUAGE_PROPERTY, project.getLanguage());
json.prop("loc", project.getLoc());
json.endObject();
});
import java.util.Locale;
import java.util.Map;
import java.util.Random;
+import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.commons.codec.digest.DigestUtils;
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.sonar.server.telemetry.TelemetryDataJsonWriter.SCIM_PROPERTY;
import static org.sonar.test.JsonAssert.assertJson;
@RunWith(DataProviderRunner.class)
}
@Test
- public void write_docker_flag() {
- boolean inDocker = random.nextBoolean();
+ @UseDataProvider("getFeatureFlagEnabledStates")
+ public void write_docker_flag(boolean isInDocker) {
TelemetryData data = telemetryBuilder()
- .setInDocker(inDocker)
+ .setInDocker(isInDocker)
.build();
String json = writeTelemetryData(data);
assertJson(json).isSimilarTo("{" +
- " \"docker\":" + inDocker +
+ " \"docker\":" + isInDocker +
"}");
}
+ @Test
+ @UseDataProvider("getFeatureFlagEnabledStates")
+ public void write_scim_feature_flag(boolean isScimEnabled) {
+ TelemetryData data = telemetryBuilder()
+ .setIsScimEnabled(isScimEnabled)
+ .build();
+
+ String json = writeTelemetryData(data);
+
+ assertJson(json).isSimilarTo("{" + format(" \"%s\":", SCIM_PROPERTY) + isScimEnabled + "}");
+ }
+
@Test
public void writes_security_custom_config() {
TelemetryData data = telemetryBuilder()
- .setCustomSecurityConfigs(Arrays.asList("php", "java"))
+ .setCustomSecurityConfigs(Set.of("php", "java"))
.build();
String json = writeTelemetryData(data);
}
return jsonString.toString();
}
+
+ @DataProvider
+ public static Set<Boolean> getFeatureFlagEnabledStates() {
+ return Set.of(true, false);
+ }
}
*/
package org.sonar.server.telemetry;
+import com.google.common.annotations.VisibleForTesting;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
@ServerSide
public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
- public static final String UNDETECTED = "undetected";
+ @VisibleForTesting
+ static final String SCIM_PROPERTY_ENABLED = "sonar.scim.enabled";
+ private static final String UNDETECTED = "undetected";
+
+ private static final Map<String, String> LANGUAGES_BY_SECURITY_JSON_PROPERTY_MAP = Map.of(
+ "sonar.security.config.javasecurity", "java",
+ "sonar.security.config.phpsecurity", "php",
+ "sonar.security.config.pythonsecurity", "python",
+ "sonar.security.config.roslyn.sonaranalyzer.security.cs", "csharp");
+
private final Server server;
private final DbClient dbClient;
private final PluginRepository pluginRepository;
@CheckForNull
private final LicenseReader licenseReader;
+
@Inject
public TelemetryDataLoaderImpl(Server server, DbClient dbClient, PluginRepository pluginRepository,
PlatformEditionProvider editionProvider, InternalProperties internalProperties, Configuration configuration,
Optional<String> installationDateProperty = internalProperties.read(InternalProperties.INSTALLATION_DATE);
installationDateProperty.ifPresent(s -> data.setInstallationDate(Long.valueOf(s)));
Optional<String> installationVersionProperty = internalProperties.read(InternalProperties.INSTALLATION_VERSION);
- data.setInstallationVersion(installationVersionProperty.orElse(null));
- data.setInDocker(dockerSupport.isRunningInDocker());
- return data.build();
+
+ return data
+ .setInstallationVersion(installationVersionProperty.orElse(null))
+ .setInDocker(dockerSupport.isRunningInDocker())
+ .setIsScimEnabled(isScimEnabled())
+ .build();
}
private void resolveProjectStatistics(TelemetryData.Builder data, DbSession dbSession) {
private void setSecurityCustomConfigIfPresent(TelemetryData.Builder data) {
editionProvider.get()
.filter(edition -> asList(ENTERPRISE, DATACENTER).contains(edition))
- .ifPresent(edition -> {
- List<String> customSecurityConfigs = new LinkedList<>();
- configuration.get("sonar.security.config.javasecurity")
- .ifPresent(s -> customSecurityConfigs.add("java"));
- configuration.get("sonar.security.config.phpsecurity")
- .ifPresent(s -> customSecurityConfigs.add("php"));
- configuration.get("sonar.security.config.pythonsecurity")
- .ifPresent(s -> customSecurityConfigs.add("python"));
- configuration.get("sonar.security.config.roslyn.sonaranalyzer.security.cs")
- .ifPresent(s -> customSecurityConfigs.add("csharp"));
- data.setCustomSecurityConfigs(customSecurityConfigs);
- });
+ .ifPresent(edition -> data.setCustomSecurityConfigs(getCustomerSecurityConfigurations()));
}
private Map<String, String> getAnalysisPropertyByProject(DbSession dbSession, String analysisPropertyKey) {
private static String getAlmName(String alm, String url) {
if (checkIfCloudAlm(alm, ALM.GITHUB.getId(), url, "https://api.github.com")) {
return "github_cloud";
- } else if (checkIfCloudAlm(alm, ALM.GITLAB.getId(), url, "https://gitlab.com/api/v4")) {
+ }
+
+ if (checkIfCloudAlm(alm, ALM.GITLAB.getId(), url, "https://gitlab.com/api/v4")) {
return "gitlab_cloud";
- } else if (checkIfCloudAlm(alm, ALM.AZURE_DEVOPS.getId(), url, "https://dev.azure.com")) {
+ }
+
+ if (checkIfCloudAlm(alm, ALM.AZURE_DEVOPS.getId(), url, "https://dev.azure.com")) {
return "azure_devops_cloud";
- } else if (ALM.BITBUCKET_CLOUD.getId().equals(alm)) {
+ }
+
+ if (ALM.BITBUCKET_CLOUD.getId().equals(alm)) {
return alm;
}
+
return alm + "_server";
}
public String loadServerId() {
return server.getId();
}
+
+ private Set<String> getCustomerSecurityConfigurations() {
+ return LANGUAGES_BY_SECURITY_JSON_PROPERTY_MAP.keySet().stream()
+ .filter(this::isPropertyPresentInConfiguration)
+ .map(LANGUAGES_BY_SECURITY_JSON_PROPERTY_MAP::get)
+ .collect(Collectors.toSet());
+ }
+
+ private boolean isPropertyPresentInConfiguration(String property) {
+ return configuration.get(property).isPresent();
+ }
+
+ private boolean isScimEnabled() {
+ return this.configuration.getBoolean(SCIM_PROPERTY_ENABLED).orElse(false);
+ }
}
*/
package org.sonar.server.telemetry;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.sonar.api.config.Configuration;
import org.sonar.api.impl.utils.TestSystem2;
import org.sonar.core.platform.PlatformEditionProvider;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.SnapshotDto;
import org.sonar.db.metric.MetricDto;
+import org.sonar.db.user.UserDbTester;
import org.sonar.db.user.UserDto;
import org.sonar.db.user.UserTelemetryDto;
import org.sonar.server.platform.DockerSupport;
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.telemetry.TelemetryDataLoaderImpl.SCIM_PROPERTY_ENABLED;
+@RunWith(DataProviderRunner.class)
public class TelemetryDataLoaderImplTest {
private final static Long NOW = 100_000_000L;
private final TestSystem2 system2 = new TestSystem2().setNow(NOW);
when(pluginRepository.getPluginInfos()).thenReturn(plugins);
when(editionProvider.get()).thenReturn(Optional.of(DEVELOPER));
- int activeUserCount = 3;
- List<UserDto> activeUsers = IntStream.range(0, activeUserCount).mapToObj(i -> db.users().insertUser(
- u -> u.setExternalIdentityProvider("provider" + i).setLastSonarlintConnectionDate(i * 2L)))
- .collect(Collectors.toList());
+ List<UserDto> activeUsers = composeActiveUsers(3);
// update last connection
activeUsers.forEach(u -> db.users().updateLastConnectionDate(u, 5L));
tuple(1L, 0L, "scm-2", "ci-2", "github_cloud"));
}
+ private List<UserDto> composeActiveUsers(int count) {
+ UserDbTester userDbTester = db.users();
+ Function<Integer, Consumer<UserDto>> userConfigurator = index -> user -> user.setExternalIdentityProvider("provider" + index).setLastSonarlintConnectionDate(index * 2L);
+
+ return IntStream
+ .rangeClosed(1, count)
+ .mapToObj(userConfigurator::apply)
+ .map(userDbTester::insertUser)
+ .collect(Collectors.toList());
+ }
+
private void assertDatabaseMetadata(TelemetryData.Database database) {
try (DbSession dbSession = db.getDbClient().openSession(false)) {
DatabaseMetaData metadata = dbSession.getConnection().getMetaData();
.containsExactlyInAnyOrder(tuple("undetected", "undetected", "undetected"));
}
+ @Test
+ @UseDataProvider("getScimFeatureStatues")
+ public void detect_scim_feature_status(boolean isEnabled) {
+ db.components().insertPublicProject();
+ when(configuration.getBoolean(SCIM_PROPERTY_ENABLED)).thenReturn(Optional.of(isEnabled));
+
+ TelemetryData data = communityUnderTest.load();
+
+ assertThat(data.isScimEnabled()).isEqualTo(isEnabled);
+ }
+
private PluginInfo newPlugin(String key, String version) {
return new PluginInfo(key)
.setVersion(Version.create(version));
.setCreatedAt(1L));
}
+ @DataProvider
+ public static Set<Boolean> getScimFeatureStatues() {
+ return Set.of(true, false);
+ }
}