Browse Source

SONAR-14518 Telemetry - include number of sonarlint users

tags/8.8.0.42792
Duarte Meneses 3 years ago
parent
commit
4558e5786f
32 changed files with 572 additions and 12 deletions
  1. 10
    1
      server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java
  2. 18
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDto.java
  3. 4
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/user/UserMapper.java
  4. 17
    0
      server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml
  5. 2
    1
      server/sonar-db-dao/src/schema/schema-sq.ddl
  6. 20
    1
      server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java
  7. 2
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java
  8. 39
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v88/AddLastSonarlintConnectionToUsers.java
  9. 33
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v88/DbVersion88.java
  10. 1
    1
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v87/DbVersion87Test.java
  11. 43
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v88/AddLastSonarlintConnectionToUsersTest.java
  12. 42
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v88/DbVersion88Test.java
  13. 28
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v88/AddLastSonarlintConnectionToUsersTest/schema.sql
  14. 12
    0
      server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java
  15. 1
    0
      server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java
  16. 10
    0
      server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java
  17. 6
    0
      server/sonar-webserver-auth/src/main/java/org/sonar/server/user/AbstractUserSession.java
  18. 7
    1
      server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ServerUserSession.java
  19. 6
    0
      server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java
  20. 3
    0
      server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSession.java
  21. 6
    0
      server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ServerUserSessionTest.java
  22. 2
    0
      server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ThreadLocalUserSessionTest.java
  23. 14
    0
      server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/MockUserSession.java
  24. 6
    0
      server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java
  25. 1
    1
      server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java
  26. 3
    2
      server/sonar-webserver-core/src/test/java/org/sonar/server/platform/ClusterSystemInfoWriterTest.java
  27. 2
    1
      server/sonar-webserver-core/src/test/java/org/sonar/server/platform/StandaloneSystemInfoWriterTest.java
  28. 16
    2
      server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java
  29. 2
    0
      server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
  30. 94
    0
      server/sonar-webserver/src/main/java/org/sonar/server/platform/web/SonarLintConnectionFilter.java
  31. 0
    1
      server/sonar-webserver/src/main/java/org/sonar/server/platform/web/WebServiceFilter.java
  32. 122
    0
      server/sonar-webserver/src/test/java/org/sonar/server/platform/web/SonarLintConnectionFilterTest.java

+ 10
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java View File

@@ -43,7 +43,7 @@ import static org.sonar.db.DatabaseUtils.executeLargeInputsWithoutOutput;
import static org.sonar.db.user.UserDto.SCM_ACCOUNTS_SEPARATOR;

public class UserDao implements Dao {
private final static long WEEK_IN_MS = 7L * 24L * 3_600L * 1_000L;
private final System2 system2;
private final UuidFactory uuidFactory;

@@ -112,6 +112,10 @@ public class UserDao implements Dao {
return dto;
}

public void updateSonarlintLastConnectionDate(DbSession session, String login) {
mapper(session).updateSonarlintLastConnectionDate(login, system2.now());
}

public void setRoot(DbSession session, String login, boolean root) {
mapper(session).setRoot(login, root, system2.now());
}
@@ -171,6 +175,11 @@ public class UserDao implements Dao {
return mapper(dbSession).selectByExternalLoginAndIdentityProvider(externalLogin, externalIdentityProvider);
}

public long countSonarlintWeeklyUsers(DbSession dbSession) {
long threshold = system2.now() - WEEK_IN_MS;
return mapper(dbSession).countActiveSonarlintUsers(threshold);
}

public void scrollByUuids(DbSession dbSession, Collection<String> uuids, Consumer<UserDto> consumer) {
UserMapper mapper = mapper(dbSession);


+ 18
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDto.java View File

@@ -63,6 +63,13 @@ public class UserDto {
@Nullable
private Long lastConnectionDate;

/**
* Date of the last time sonarlint connected to sonarqube WSs with this user's authentication.
* Can be null when user has never been authenticated, or has not been authenticated since the creation of the column in SonarQube 8.8.
*/
@Nullable
private Long lastSonarlintConnectionDate;

private Long createdAt;
private Long updatedAt;

@@ -281,6 +288,17 @@ public class UserDto {
return this;
}

@CheckForNull
public Long getLastSonarlintConnectionDate() {
return lastSonarlintConnectionDate;
}

public UserDto setLastSonarlintConnectionDate(@Nullable Long lastSonarlintConnectionDate) {
this.lastSonarlintConnectionDate = lastSonarlintConnectionDate;
return this;
}


public Long getCreatedAt() {
return createdAt;
}

+ 4
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserMapper.java View File

@@ -66,6 +66,8 @@ public interface UserMapper {

void scrollAll(ResultHandler<UserDto> handler);

void updateSonarlintLastConnectionDate(@Param("login") String login, @Param("now") long now);

/**
* Count actives users which are root and which login is not the specified one.
*/
@@ -82,4 +84,6 @@ public interface UserMapper {
void clearHomepages(@Param("homepageType") String type, @Param("homepageParameter") String value, @Param("now") long now);

void clearHomepage(@Param("login") String login, @Param("now") long now);

long countActiveSonarlintUsers(@Param("sinceDate") long sinceDate);
}

+ 17
- 0
server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml View File

@@ -23,6 +23,7 @@
u.homepage_type as "homepageType",
u.homepage_parameter as "homepageParameter",
u.last_connection_date as "lastConnectionDate",
u.last_sonarlint_connection as "lastSonarlintConnectionDate",
u.created_at as "createdAt",
u.updated_at as "updatedAt"
</sql>
@@ -166,9 +167,17 @@
salt = null,
crypted_password = null,
last_connection_date = null,
last_sonarlint_connection = null,
updated_at = #{now, jdbcType=BIGINT}
</sql>

<update id="updateSonarlintLastConnectionDate" parameterType="map">
update users set
last_sonarlint_connection = #{now, jdbcType=BIGINT}
where
login = #{login, jdbcType=VARCHAR}
</update>

<update id="deactivateUser" parameterType="map">
update users set
<include refid="deactivateUserUpdatedFields"/>
@@ -221,6 +230,7 @@
hash_method,
is_root,
onboarded,
last_sonarlint_connection,
reset_password,
homepage_type,
homepage_parameter,
@@ -242,6 +252,7 @@
#{user.hashMethod,jdbcType=VARCHAR},
#{user.root,jdbcType=BOOLEAN},
#{user.onboarded,jdbcType=BOOLEAN},
#{user.lastSonarlintConnectionDate,jdbcType=BIGINT},
#{user.resetPassword,jdbcType=BOOLEAN},
#{user.homepageType,jdbcType=VARCHAR},
#{user.homepageParameter,jdbcType=VARCHAR},
@@ -269,9 +280,15 @@
homepage_type = #{user.homepageType, jdbcType=VARCHAR},
homepage_parameter = #{user.homepageParameter, jdbcType=VARCHAR},
last_connection_date = #{user.lastConnectionDate,jdbcType=BIGINT},
last_sonarlint_connection = #{user.lastSonarlintConnectionDate,jdbcType=BIGINT},
updated_at = #{user.updatedAt,jdbcType=BIGINT}
where
uuid = #{user.uuid, jdbcType=VARCHAR}
</update>

<select id="countActiveSonarlintUsers" parameterType="map" resultType="long">
select count(login) from users
where last_sonarlint_connection > #{sinceDate,jdbcType=BIGINT}
</select>

</mapper>

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

@@ -914,7 +914,8 @@ CREATE TABLE "USERS"(
"LAST_CONNECTION_DATE" BIGINT,
"CREATED_AT" BIGINT,
"UPDATED_AT" BIGINT,
"RESET_PASSWORD" BOOLEAN NOT NULL
"RESET_PASSWORD" BOOLEAN NOT NULL,
"LAST_SONARLINT_CONNECTION" BIGINT
);
ALTER TABLE "USERS" ADD CONSTRAINT "PK_USERS" PRIMARY KEY("UUID");
CREATE UNIQUE INDEX "USERS_LOGIN" ON "USERS"("LOGIN");

+ 20
- 1
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java View File

@@ -510,6 +510,25 @@ public class UserDaoTest {
assertThat(untouchedUserReloaded.getHomepageParameter()).isEqualTo(untouchedUser.getHomepageParameter());
}

@Test
public void update_last_sonarlint_connection_date() {
UserDto user = db.users().insertUser();
assertThat(user.getLastSonarlintConnectionDate()).isNull();
underTest.updateSonarlintLastConnectionDate(db.getSession(), user.getLogin());
assertThat(underTest.selectByLogin(db.getSession(), user.getLogin()).getLastSonarlintConnectionDate()).isEqualTo(NOW);
}

@Test
public void count_sonarlint_weekly_users() {
UserDto user1 = db.users().insertUser(c -> c.setLastSonarlintConnectionDate(NOW - 100_000));
UserDto user2 = db.users().insertUser(c -> c.setLastSonarlintConnectionDate(NOW));
// these don't count
UserDto user3 = db.users().insertUser(c -> c.setLastSonarlintConnectionDate(NOW - 1_000_000_000));
UserDto user4 = db.users().insertUser();

assertThat(underTest.countSonarlintWeeklyUsers(db.getSession())).isEqualTo(2);
}

@Test
public void clean_user_homepage() {

@@ -628,7 +647,7 @@ public class UserDaoTest {
.extracting(UserDto::getUuid).containsExactlyInAnyOrder(user1.getUuid());
assertThat(underTest.selectByExternalIdsAndIdentityProvider(session,
asList(user1.getExternalId(), user2.getExternalId(), user3.getExternalId(), disableUser.getExternalId()), "github"))
.extracting(UserDto::getUuid).containsExactlyInAnyOrder(user1.getUuid(), user2.getUuid(), disableUser.getUuid());
.extracting(UserDto::getUuid).containsExactlyInAnyOrder(user1.getUuid(), user2.getUuid(), disableUser.getUuid());
assertThat(underTest.selectByExternalIdsAndIdentityProvider(session, singletonList("unknown"), "github")).isEmpty();
assertThat(underTest.selectByExternalIdsAndIdentityProvider(session, singletonList(user1.getExternalId()), "unknown")).isEmpty();
}

+ 2
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java View File

@@ -35,6 +35,7 @@ import org.sonar.server.platform.db.migration.version.v84.DbVersion84;
import org.sonar.server.platform.db.migration.version.v85.DbVersion85;
import org.sonar.server.platform.db.migration.version.v86.DbVersion86;
import org.sonar.server.platform.db.migration.version.v87.DbVersion87;
import org.sonar.server.platform.db.migration.version.v88.DbVersion88;

public class MigrationConfigurationModule extends Module {
@Override
@@ -50,6 +51,7 @@ public class MigrationConfigurationModule extends Module {
DbVersion85.class,
DbVersion86.class,
DbVersion87.class,
DbVersion88.class,

// migration steps
MigrationStepRegistryImpl.class,

+ 39
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v88/AddLastSonarlintConnectionToUsers.java View File

@@ -0,0 +1,39 @@
/*
* SonarQube
* Copyright (C) 2009-2021 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.v88;

import java.sql.SQLException;
import org.sonar.db.Database;
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 AddLastSonarlintConnectionToUsers extends DdlChange {
public AddLastSonarlintConnectionToUsers(Database db) {
super(db);
}

@Override
public void execute(Context context) throws SQLException {
context.execute(new AddColumnsBuilder(getDialect(), "users")
.addColumn(BigIntegerColumnDef.newBigIntegerColumnDefBuilder().setColumnName("last_sonarlint_connection").setIsNullable(true).build())
.build());
}
}

+ 33
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v88/DbVersion88.java View File

@@ -0,0 +1,33 @@
/*
* SonarQube
* Copyright (C) 2009-2021 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.v88;

import org.sonar.server.platform.db.migration.step.MigrationStepRegistry;
import org.sonar.server.platform.db.migration.version.DbVersion;

public class DbVersion88 implements DbVersion {

@Override
public void addSteps(MigrationStepRegistry registry) {
registry
.add(4300, "Add 'last_sonarlint_connection' to 'users", AddLastSonarlintConnectionToUsers.class)
;
}
}

+ 1
- 1
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v87/DbVersion87Test.java View File

@@ -30,7 +30,7 @@ public class DbVersion87Test {
private final DbVersion underTest = new DbVersion87();

@Test
public void migrationNumber_starts_at_4100() {
public void migrationNumber_starts_at_4200() {
verifyMinimumMigrationNumber(underTest, 4200);
}


+ 43
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v88/AddLastSonarlintConnectionToUsersTest.java View File

@@ -0,0 +1,43 @@
/*
* SonarQube
* Copyright (C) 2009-2021 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.v88;

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 AddLastSonarlintConnectionToUsersTest {
private static final String TABLE_NAME = "users";
private static final String COLUMN_NAME = "last_sonarlint_connection";

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

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

+ 42
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v88/DbVersion88Test.java View File

@@ -0,0 +1,42 @@
/*
* SonarQube
* Copyright (C) 2009-2021 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.v88;

import org.junit.Test;
import org.sonar.server.platform.db.migration.version.DbVersion;

import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMigrationNotEmpty;
import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMinimumMigrationNumber;

public class DbVersion88Test {

private final DbVersion underTest = new DbVersion88();

@Test
public void migrationNumber_starts_at_4300() {
verifyMinimumMigrationNumber(underTest, 4300);
}

@Test
public void verify_migration_count() {
verifyMigrationNotEmpty(underTest);
}

}

+ 28
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v88/AddLastSonarlintConnectionToUsersTest/schema.sql View File

@@ -0,0 +1,28 @@
CREATE TABLE "USERS"(
"UUID" VARCHAR(255) NOT NULL,
"LOGIN" VARCHAR(255) NOT NULL,
"NAME" VARCHAR(200),
"EMAIL" VARCHAR(100),
"CRYPTED_PASSWORD" VARCHAR(100),
"SALT" VARCHAR(40),
"HASH_METHOD" VARCHAR(10),
"ACTIVE" BOOLEAN DEFAULT TRUE,
"SCM_ACCOUNTS" VARCHAR(4000),
"EXTERNAL_LOGIN" VARCHAR(255) NOT NULL,
"EXTERNAL_IDENTITY_PROVIDER" VARCHAR(100) NOT NULL,
"EXTERNAL_ID" VARCHAR(255) NOT NULL,
"IS_ROOT" BOOLEAN NOT NULL,
"USER_LOCAL" BOOLEAN,
"ONBOARDED" BOOLEAN NOT NULL,
"HOMEPAGE_TYPE" VARCHAR(40),
"HOMEPAGE_PARAMETER" VARCHAR(40),
"LAST_CONNECTION_DATE" BIGINT,
"CREATED_AT" BIGINT,
"UPDATED_AT" BIGINT,
"RESET_PASSWORD" BOOLEAN NOT NULL
);
ALTER TABLE "USERS" ADD CONSTRAINT "PK_USERS" PRIMARY KEY("UUID");
CREATE UNIQUE INDEX "USERS_LOGIN" ON "USERS"("LOGIN");
CREATE INDEX "USERS_UPDATED_AT" ON "USERS"("UPDATED_AT");
CREATE UNIQUE INDEX "UNIQ_EXTERNAL_ID" ON "USERS"("EXTERNAL_IDENTITY_PROVIDER", "EXTERNAL_ID");
CREATE UNIQUE INDEX "UNIQ_EXTERNAL_LOGIN" ON "USERS"("EXTERNAL_IDENTITY_PROVIDER", "EXTERNAL_LOGIN");

+ 12
- 0
server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java View File

@@ -52,6 +52,7 @@ public class TelemetryData {
private final Boolean hasUnanalyzedC;
private final Boolean hasUnanalyzedCpp;
private final List<String> customSecurityConfigs;
private final long sonarlintWeeklyUsers;

private TelemetryData(Builder builder) {
serverId = builder.serverId;
@@ -62,6 +63,7 @@ public class TelemetryData {
projectCount = builder.projectMeasuresStatistics.getProjectCount();
usingBranches = builder.usingBranches;
database = builder.database;
sonarlintWeeklyUsers = builder.sonarlintWeeklyUsers;
projectCountByLanguage = builder.projectMeasuresStatistics.getProjectCountByLanguage();
almIntegrationCountByAlm = builder.almIntegrationCountByAlm;
nclocByLanguage = builder.projectMeasuresStatistics.getNclocByLanguage();
@@ -93,6 +95,10 @@ public class TelemetryData {
return ncloc;
}

public long sonarlintWeeklyUsers() {
return sonarlintWeeklyUsers;
}

public long getUserCount() {
return userCount;
}
@@ -169,6 +175,7 @@ public class TelemetryData {
private String serverId;
private String version;
private long userCount;
private long sonarlintWeeklyUsers;
private Map<String, String> plugins;
private Database database;
private ProjectMeasuresStatistics projectMeasuresStatistics;
@@ -201,6 +208,11 @@ public class TelemetryData {
return this;
}

Builder setSonarlintWeeklyUsers(long sonarlintWeeklyUsers) {
this.sonarlintWeeklyUsers = sonarlintWeeklyUsers;
return this;
}

Builder setServerId(String serverId) {
this.serverId = serverId;
return this;

+ 1
- 0
server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java View File

@@ -105,6 +105,7 @@ public class TelemetryDataJsonWriter {
});
json.endArray();

json.prop("sonarlintWeeklyUsers", statistics.sonarlintWeeklyUsers());
if (statistics.getInstallationDate() != null) {
json.prop("installationDate", statistics.getInstallationDate());
}

+ 10
- 0
server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java View File

@@ -59,6 +59,7 @@ public class TelemetryDataJsonWriterTest {
.setNcloc(42L)
.setExternalAuthenticationProviders(asList("github", "gitlab"))
.setProjectCountByScm(Collections.emptyMap())
.setSonarlintWeeklyUsers(10)
.setDatabase(new TelemetryData.Database("H2", "11"))
.setUsingBranches(true);

@@ -96,6 +97,15 @@ public class TelemetryDataJsonWriterTest {
assertJson(json).isSimilarTo("{ \"externalAuthProviders\": [ \"github\", \"gitlab\" ] }");
}

@Test
public void write_sonarlint_weekly_users() {
TelemetryData data = SOME_TELEMETRY_DATA.build();

String json = writeTelemetryData(data);

assertJson(json).isSimilarTo("{ \"sonarlintWeeklyUsers\": 10 }");
}

@Test
@UseDataProvider("allEditions")
public void writes_edition_if_non_null(EditionProvider.Edition edition) {

+ 6
- 0
server/sonar-webserver-auth/src/main/java/org/sonar/server/user/AbstractUserSession.java View File

@@ -75,6 +75,12 @@ public abstract class AbstractUserSession implements UserSession {
}
}

@Override
@CheckForNull
public Long getLastSonarlintConnectionDate() {
return null;
}

@Override
public final boolean hasPermission(GlobalPermission permission) {
return isRoot() || hasPermissionImpl(permission);

+ 7
- 1
server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ServerUserSession.java View File

@@ -55,7 +55,7 @@ public class ServerUserSession extends AbstractUserSession {
private Set<GlobalPermission> permissions;
private Map<String, Set<String>> permissionsByProjectUuid;

ServerUserSession(DbClient dbClient, @Nullable UserDto userDto) {
public ServerUserSession(DbClient dbClient, @Nullable UserDto userDto) {
this.dbClient = dbClient;
this.userDto = userDto;
}
@@ -69,6 +69,12 @@ public class ServerUserSession extends AbstractUserSession {
}
}

@Override
@CheckForNull
public Long getLastSonarlintConnectionDate() {
return userDto == null ? null : userDto.getLastSonarlintConnectionDate();
}

@Override
@CheckForNull
public String getLogin() {

+ 6
- 0
server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java View File

@@ -56,6 +56,12 @@ public class ThreadLocalUserSession implements UserSession {
return DELEGATE.get() != null;
}

@Override
@CheckForNull
public Long getLastSonarlintConnectionDate() {
return get().getLastSonarlintConnectionDate();
}

@Override
@CheckForNull
public String getLogin() {

+ 3
- 0
server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSession.java View File

@@ -56,6 +56,9 @@ public interface UserSession {
@CheckForNull
String getName();

@CheckForNull
Long getLastSonarlintConnectionDate();

/**
* The groups that the logged-in user is member of. An empty
* collection is returned if {@link #isLoggedIn()} is {@code false}.

+ 6
- 0
server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ServerUserSessionTest.java View File

@@ -99,6 +99,12 @@ public class ServerUserSessionTest {
assertThat(newUserSession(user).getGroups()).extracting(GroupDto::getUuid).containsOnly(group1.getUuid(), group2.getUuid());
}

@Test
public void getLastSonarlintConnectionDate() {
UserDto user = db.users().insertUser(p -> p.setLastSonarlintConnectionDate(1000L));
assertThat(newUserSession(user).getLastSonarlintConnectionDate()).isEqualTo(1000L);
}

@Test
public void getGroups_keeps_groups_in_cache() {
UserDto user = db.users().insertUser();

+ 2
- 0
server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ThreadLocalUserSessionTest.java View File

@@ -53,11 +53,13 @@ public class ThreadLocalUserSessionTest {
MockUserSession expected = new MockUserSession("karadoc")
.setUuid("karadoc-uuid")
.setResetPassword(true)
.setLastSonarlintConnectionDate(1000L)
.setGroups(group);
threadLocalUserSession.set(expected);

UserSession session = threadLocalUserSession.get();
assertThat(session).isSameAs(expected);
assertThat(threadLocalUserSession.getLastSonarlintConnectionDate()).isEqualTo(1000L);
assertThat(threadLocalUserSession.getLogin()).isEqualTo("karadoc");
assertThat(threadLocalUserSession.getUuid()).isEqualTo("karadoc-uuid");
assertThat(threadLocalUserSession.isLoggedIn()).isTrue();

+ 14
- 0
server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/MockUserSession.java View File

@@ -23,6 +23,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import javax.annotation.CheckForNull;
import org.jetbrains.annotations.Nullable;
import org.sonar.db.user.GroupDto;
import org.sonar.db.user.UserDto;
import org.sonar.server.user.AbstractUserSession;
@@ -41,6 +43,7 @@ public class MockUserSession extends AbstractMockUserSession<MockUserSession> {
private List<GroupDto> groups = new ArrayList<>();
private UserSession.IdentityProvider identityProvider;
private UserSession.ExternalIdentity externalIdentity;
private Long lastSonarlintConnectionDate;

public MockUserSession(String login) {
super(MockUserSession.class);
@@ -62,6 +65,17 @@ public class MockUserSession extends AbstractMockUserSession<MockUserSession> {
this.externalIdentity = identity.getExternalIdentity();
}

@CheckForNull
@Override
public Long getLastSonarlintConnectionDate() {
return lastSonarlintConnectionDate;
}

public MockUserSession setLastSonarlintConnectionDate(@Nullable Long lastSonarlintConnectionDate) {
this.lastSonarlintConnectionDate = lastSonarlintConnectionDate;
return this;
}

@Override
public boolean isLoggedIn() {
return true;

+ 6
- 0
server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java View File

@@ -278,6 +278,12 @@ public class UserSessionRule implements TestRule, UserSession {
return currentUserSession.getName();
}

@Override
@CheckForNull
public Long getLastSonarlintConnectionDate() {
return currentUserSession.getLastSonarlintConnectionDate();
}

@Override
public Collection<GroupDto> getGroups() {
return currentUserSession.getGroups();

+ 1
- 1
server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java View File

@@ -144,6 +144,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
.stream()
.collect(Collectors.toMap(ProjectCountPerAnalysisPropertyValue::getPropertyValue, ProjectCountPerAnalysisPropertyValue::getCount));
data.setProjectCountByScm(projectCountPerScmDetected);
data.setSonarlintWeeklyUsers(dbClient.userDao().countSonarlintWeeklyUsers(dbSession));
}

setSecurityCustomConfigIfPresent(data);
@@ -153,7 +154,6 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
Optional<String> installationVersionProperty = internalProperties.read(InternalProperties.INSTALLATION_VERSION);
data.setInstallationVersion(installationVersionProperty.orElse(null));
data.setInDocker(dockerSupport.isRunningInDocker());

return data.build();
}


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

@@ -73,8 +73,9 @@ public class ClusterSystemInfoWriterTest {
+ "\"Application Nodes\":[{\"Name\":\"appNodes\",\"\":{\"name\":\"appNodes\"}}],"
+ "\"Search Nodes\":[{\"Name\":\"searchNodes\",\"\":{\"name\":\"searchNodes\"}}],"
+ "\"Statistics\":{\"id\":\"\",\"version\":\"\",\"database\":{\"name\":\"\",\"version\":\"\"},\"plugins\":[],"
+ "\"userCount\":0,\"projectCount\":0,\"usingBranches\":false,\"ncloc\":0,\"projectCountByLanguage\":[]," +
"\"nclocByLanguage\":[],\"almIntegrationCount\":[],\"externalAuthProviders\":[],\"projectCountByScm\":[],\"installationDate\":0,\"installationVersion\":\"\",\"docker\":false}}");
+ "\"userCount\":0,\"projectCount\":0,\"usingBranches\":false,\"ncloc\":0,\"projectCountByLanguage\":[],"
+ "\"nclocByLanguage\":[],\"almIntegrationCount\":[],\"externalAuthProviders\":[],\"projectCountByScm\":[],\"sonarlintWeeklyUsers\":0,\"installationDate\":0,"
+ "\"installationVersion\":\"\",\"docker\":false}}");
}

private static NodeInfo createNodeInfo(String name) {

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

@@ -80,7 +80,8 @@ public class StandaloneSystemInfoWriterTest {
// response does not contain empty "Section Three"
assertThat(writer).hasToString("{\"Health\":\"GREEN\",\"Health Causes\":[],\"Section One\":{\"foo\":\"bar\"},\"Section Two\":{\"one\":1,\"two\":2}," +
"\"Statistics\":{\"id\":\"\",\"version\":\"\",\"database\":{\"name\":\"\",\"version\":\"\"},\"plugins\":[],\"userCount\":0,\"projectCount\":0,\"usingBranches\":false," +
"\"ncloc\":0,\"projectCountByLanguage\":[],\"nclocByLanguage\":[],\"almIntegrationCount\":[],\"externalAuthProviders\":[],\"projectCountByScm\":[],\"installationDate\":0,\"installationVersion\":\"\",\"docker\":false}}");
"\"ncloc\":0,\"projectCountByLanguage\":[],\"nclocByLanguage\":[],\"almIntegrationCount\":[],\"externalAuthProviders\":[],\"projectCountByScm\":[],"
+ "\"sonarlintWeeklyUsers\":0,\"installationDate\":0,\"installationVersion\":\"\",\"docker\":false}}");
}

private void logInAsSystemAdministrator() {

+ 16
- 2
server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java View File

@@ -66,15 +66,17 @@ import static org.sonar.server.metric.UnanalyzedLanguageMetrics.UNANALYZED_CPP_K
import static org.sonar.server.metric.UnanalyzedLanguageMetrics.UNANALYZED_C_KEY;

public class TelemetryDataLoaderImplTest {
private final static Long NOW = 100_000_000L;
private final TestSystem2 system2 = new TestSystem2().setNow(NOW);

@Rule
public DbTester db = DbTester.create();
public DbTester db = DbTester.create(system2);
@Rule
public EsTester es = EsTester.create();

private final FakeServer server = new FakeServer();
private final PluginRepository pluginRepository = mock(PluginRepository.class);
private final Configuration configuration = mock(Configuration.class);
private final TestSystem2 system2 = new TestSystem2().setNow(System.currentTimeMillis());
private final PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class);
private final DockerSupport dockerSupport = mock(DockerSupport.class);
private final InternalProperties internalProperties = spy(new MapInternalProperties());
@@ -201,6 +203,18 @@ public class TelemetryDataLoaderImplTest {
assertThat(data.getLicenseType()).isEmpty();
}

@Test
public void data_contains_weekly_count_sonarlint_users() {
db.users().insertUser(c -> c.setLastSonarlintConnectionDate(NOW - 100_000L));
db.users().insertUser(c -> c.setLastSonarlintConnectionDate(NOW));
// these don't count
db.users().insertUser(c -> c.setLastSonarlintConnectionDate(NOW - 1_000_000_000L));
db.users().insertUser();

TelemetryData data = communityUnderTest.load();
assertThat(data.sonarlintWeeklyUsers()).isEqualTo(2L);
}

@Test
public void data_has_license_type_on_commercial_edition_if_no_license() {
String licenseType = randomAlphabetic(12);

+ 2
- 0
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java View File

@@ -133,6 +133,7 @@ import org.sonar.server.platform.ClusterVerification;
import org.sonar.server.platform.PersistentSettings;
import org.sonar.server.platform.SystemInfoWriterModule;
import org.sonar.server.platform.WebCoreExtensionsInstaller;
import org.sonar.server.platform.web.SonarLintConnectionFilter;
import org.sonar.server.platform.web.WebServiceFilter;
import org.sonar.server.platform.web.WebServiceReroutingFilter;
import org.sonar.server.platform.web.requestid.HttpRequestIdModule;
@@ -338,6 +339,7 @@ public class PlatformLevel4 extends PlatformLevel {
// web services
WebServiceEngine.class,
WebServicesWsModule.class,
SonarLintConnectionFilter.class,
WebServiceFilter.class,
WebServiceReroutingFilter.class,


+ 94
- 0
server/sonar-webserver/src/main/java/org/sonar/server/platform/web/SonarLintConnectionFilter.java View File

@@ -0,0 +1,94 @@
/*
* SonarQube
* Copyright (C) 2009-2021 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.web;

import java.io.IOException;
import java.util.Locale;
import java.util.Optional;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.sonar.api.utils.System2;
import org.sonar.api.web.ServletFilter;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.server.user.UserSession;
import org.sonar.server.ws.ServletRequest;

public class SonarLintConnectionFilter extends ServletFilter {
private static final UrlPattern URL_PATTERN = UrlPattern.builder()
.includes("/api/*")
.build();
private final DbClient dbClient;
private final UserSession userSession;
private final System2 system2;

public SonarLintConnectionFilter(DbClient dbClient, UserSession userSession, System2 system2) {
this.dbClient = dbClient;
this.userSession = userSession;
this.system2 = system2;
}

@Override
public UrlPattern doGetPattern() {
return URL_PATTERN;
}

@Override
public void doFilter(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
ServletRequest wsRequest = new ServletRequest(request);

Optional<String> agent = wsRequest.header("User-Agent");
if (agent.isPresent() && agent.get().toLowerCase(Locale.ENGLISH).contains("sonarlint")) {
update();
}
chain.doFilter(servletRequest, servletResponse);
}

@Override
public void init(FilterConfig filterConfig) {
// Nothing to do
}

@Override
public void destroy() {
// Nothing to do
}

public void update() {
if (shouldUpdate()) {
try (DbSession session = dbClient.openSession(false)) {
dbClient.userDao().updateSonarlintLastConnectionDate(session, userSession.getLogin());
session.commit();
}
}
}

private boolean shouldUpdate() {
if (!userSession.isLoggedIn()) {
return false;
}
long now = system2.now();
Long lastUpdate = userSession.getLastSonarlintConnectionDate();
return (lastUpdate == null || lastUpdate < now - 3_600_000L);
}
}

+ 0
- 1
server/sonar-webserver/src/main/java/org/sonar/server/platform/web/WebServiceFilter.java View File

@@ -26,7 +26,6 @@ import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.sonar.api.SonarRuntime;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.web.ServletFilter;
import org.sonar.core.util.stream.MoreCollectors;

+ 122
- 0
server/sonar-webserver/src/test/java/org/sonar/server/platform/web/SonarLintConnectionFilterTest.java View File

@@ -0,0 +1,122 @@
/*
* SonarQube
* Copyright (C) 2009-2021 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.web;

import java.io.IOException;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.impl.utils.TestSystem2;
import org.sonar.db.DbTester;
import org.sonar.db.user.UserDto;
import org.sonar.server.user.ServerUserSession;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class SonarLintConnectionFilterTest {
private static final String LOGIN = "user1";
private final TestSystem2 system2 = new TestSystem2();

@Rule
public DbTester dbTester = DbTester.create(system2);

@Test
public void update() throws IOException, ServletException {
system2.setNow(10_000_000L);
addUser(LOGIN, 1_000_000L);

runFilter(LOGIN, "SonarLint for IntelliJ");
assertThat(getLastUpdate(LOGIN)).isEqualTo(10_000_000L);
}

@Test
public void update_first_time() throws IOException, ServletException {
system2.setNow(10_000_000L);
addUser(LOGIN, null);

runFilter(LOGIN, "SonarLint for IntelliJ");
assertThat(getLastUpdate(LOGIN)).isEqualTo(10_000_000L);
}

@Test
public void only_applies_to_api() {
SonarLintConnectionFilter underTest = new SonarLintConnectionFilter(dbTester.getDbClient(), mock(ServerUserSession.class), system2);
assertThat(underTest.doGetPattern().matches("/api/test")).isTrue();
assertThat(underTest.doGetPattern().matches("/test")).isFalse();

}

@Test
public void do_nothing_if_no_sonarlint_agent() throws IOException, ServletException {
system2.setNow(10_000L);
addUser(LOGIN, 1_000L);

runFilter(LOGIN, "unknown");
runFilter(LOGIN, null);
assertThat(getLastUpdate(LOGIN)).isEqualTo(1_000L);
}

@Test
public void do_nothing_if_not_logged_in() throws IOException, ServletException {
system2.setNow(10_000_000L);
addUser("invalid", 1_000_000L);

runFilter(LOGIN, "SonarLint for IntelliJ");
assertThat(getLastUpdate("invalid")).isEqualTo(1_000_000L);
}

@Test
public void only_update_if_not_updated_within_1h() throws IOException, ServletException {
system2.setNow(2_000_000L);
addUser(LOGIN, 1_000_000L);

runFilter(LOGIN, "SonarLint for IntelliJ");
assertThat(getLastUpdate(LOGIN)).isEqualTo(1_000_000L);
}

private void addUser(String login, @Nullable Long lastUpdate) {
dbTester.users().insertUser(u -> u.setLogin(login).setLastSonarlintConnectionDate(lastUpdate));
}

@CheckForNull
private Long getLastUpdate(String login) {
return dbTester.getDbClient().userDao().selectByLogin(dbTester.getSession(), login).getLastSonarlintConnectionDate();
}

private void runFilter(String loggedInUser, @Nullable String agent) throws IOException, ServletException {
UserDto user = dbTester.getDbClient().userDao().selectByLogin(dbTester.getSession(), loggedInUser);
ServerUserSession session = new ServerUserSession(dbTester.getDbClient(), user);
SonarLintConnectionFilter underTest = new SonarLintConnectionFilter(dbTester.getDbClient(), session, system2);
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getHeader("User-Agent")).thenReturn(agent);
FilterChain chain = mock(FilterChain.class);
underTest.doFilter(request, mock(ServletResponse.class), chain);
verify(chain).doFilter(any(), any());
}
}

Loading…
Cancel
Save