]> source.dussan.org Git - sonarqube.git/commitdiff
[SONAR-18964] add sonarLintLastConnectionDate parameter to api/users/search response...
authorSteve Marion <steve.marion@sonarsource.com>
Tue, 11 Apr 2023 13:12:21 +0000 (15:12 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 12 Apr 2023 20:03:18 +0000 (20:03 +0000)
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserQuery.java
server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserDbTester.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/user/ws/SearchActionIT.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SearchAction.java
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/user/ws/search-example.json
sonar-ws/src/main/protobuf/ws-users.proto

index 43d326ec43cfb99f2450691509da9370ed8e1c17..54f8e0945ccdc02d2faeb1ccc809a1c5823cc710 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonar.db.user;
 
+import java.time.OffsetDateTime;
+import java.time.temporal.ChronoUnit;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.apache.commons.lang.StringUtils;
@@ -27,11 +29,37 @@ public class UserQuery {
   private final String searchText;
   private final Boolean isActive;
   private final String isManagedSqlClause;
+  private final Long lastConnectionDateFrom;
+  private final Long lastConnectionDateTo;
+  private final Long sonarLintLastConnectionDateFrom;
+  private final Long sonarLintLastConnectionDateTo;
 
-  public UserQuery(@Nullable String searchText, @Nullable Boolean isActive, @Nullable String isManagedSqlClause) {
+  public UserQuery(@Nullable String searchText, @Nullable Boolean isActive, @Nullable String isManagedSqlClause,
+    @Nullable OffsetDateTime lastConnectionDateFrom, @Nullable OffsetDateTime lastConnectionDateTo,
+    @Nullable OffsetDateTime sonarLintLastConnectionDateFrom, @Nullable OffsetDateTime sonarLintLastConnectionDateTo) {
     this.searchText = searchTextToSearchTextSql(searchText);
     this.isActive = isActive;
     this.isManagedSqlClause = isManagedSqlClause;
+    this.lastConnectionDateFrom = parseDateToLong(lastConnectionDateFrom);
+    this.lastConnectionDateTo = formatDateToInput(lastConnectionDateTo);
+    this.sonarLintLastConnectionDateFrom = parseDateToLong(sonarLintLastConnectionDateFrom);
+    this.sonarLintLastConnectionDateTo = formatDateToInput(sonarLintLastConnectionDateTo);
+  }
+
+  private static Long formatDateToInput(@Nullable OffsetDateTime dateTo) {
+    if(dateTo == null) {
+      return null;
+    } else {
+      // add 1 second to include all timestamp at the second precision.
+      return dateTo.toInstant().plus(1, ChronoUnit.SECONDS).toEpochMilli();
+    }
+  }
+  private static Long parseDateToLong(@Nullable OffsetDateTime date) {
+    if(date == null) {
+      return null;
+    } else {
+      return date.toInstant().toEpochMilli();
+    }
   }
 
   private static String searchTextToSearchTextSql(@Nullable String text) {
@@ -59,6 +87,24 @@ public class UserQuery {
     return isManagedSqlClause;
   }
 
+  @CheckForNull
+  public Long getLastConnectionDateFrom() {
+    return lastConnectionDateFrom;
+  }
+
+  @CheckForNull
+  public Long getLastConnectionDateTo() {
+    return lastConnectionDateTo;
+  }
+  @CheckForNull
+  public Long getSonarLintLastConnectionDateFrom() {
+    return sonarLintLastConnectionDateFrom;
+  }
+  @CheckForNull
+  public Long getSonarLintLastConnectionDateTo() {
+    return sonarLintLastConnectionDateTo;
+  }
+
   public static UserQueryBuilder builder() {
     return new UserQueryBuilder();
   }
@@ -67,6 +113,11 @@ public class UserQuery {
     private String searchText = null;
     private Boolean isActive = null;
     private String isManagedSqlClause = null;
+    private OffsetDateTime lastConnectionDateFrom = null;
+    private OffsetDateTime lastConnectionDateTo = null;
+    private OffsetDateTime sonarLintLastConnectionDateFrom = null;
+    private OffsetDateTime sonarLintLastConnectionDateTo = null;
+
 
     private UserQueryBuilder() {
     }
@@ -86,8 +137,30 @@ public class UserQuery {
       return this;
     }
 
+    public UserQueryBuilder lastConnectionDateFrom(@Nullable OffsetDateTime lastConnectionDateFrom) {
+      this.lastConnectionDateFrom = lastConnectionDateFrom;
+      return this;
+    }
+
+    public UserQueryBuilder lastConnectionDateTo(@Nullable OffsetDateTime lastConnectionDateTo) {
+      this.lastConnectionDateTo = lastConnectionDateTo;
+      return this;
+    }
+
+    public UserQueryBuilder sonarLintLastConnectionDateFrom(@Nullable OffsetDateTime sonarLintLastConnectionDateFrom) {
+      this.sonarLintLastConnectionDateFrom = sonarLintLastConnectionDateFrom;
+      return this;
+    }
+
+    public UserQueryBuilder sonarLintLastConnectionDateTo(@Nullable OffsetDateTime sonarLintLastConnectionDateTo) {
+      this.sonarLintLastConnectionDateTo = sonarLintLastConnectionDateTo;
+      return this;
+    }
+
     public UserQuery build() {
-      return new UserQuery(searchText, isActive, isManagedSqlClause);
+      return new UserQuery(
+        searchText, isActive, isManagedSqlClause, lastConnectionDateFrom, lastConnectionDateTo,
+        sonarLintLastConnectionDateFrom, sonarLintLastConnectionDateTo);
     }
   }
 }
index 4042833f53226e2795bd78b4075bdf84c382385e..791f63c990c7d494b421ff11ecbe33dbd847dc4f 100644 (file)
             <if test="query.isManagedSqlClause != null">
                 AND ${query.isManagedSqlClause}
             </if>
+            <if test="query.lastConnectionDateFrom != null">
+                AND u.last_connection_date &gt;= #{query.lastConnectionDateFrom, jdbcType=BIGINT}
+            </if>
+            <if test="query.lastConnectionDateTo != null">
+                AND u.last_connection_date &lt; #{query.lastConnectionDateTo, jdbcType=BIGINT}
+            </if>
+            <if test="query.sonarLintLastConnectionDateFrom != null">
+                AND u.last_sonarlint_connection &gt;= #{query.sonarLintLastConnectionDateFrom, jdbcType=BIGINT}
+            </if>
+            <if test="query.sonarLintLastConnectionDateTo != null">
+                AND u.last_sonarlint_connection &lt; #{query.sonarLintLastConnectionDateTo, jdbcType=BIGINT}
+            </if>
         </where>
     </sql>
 
index 846b20a7c9cc1f1803b9cfdfd13127c56cfceaeb..c6f99bd5e7f18dddb496a79f98046a0cbcd50cd8 100644 (file)
@@ -115,6 +115,12 @@ public class UserDbTester {
     return user;
   }
 
+  public UserDto updateSonarLintLastConnectionDate(UserDto user, long sonarLintLastConnectionDate) {
+    db.getDbClient().userDao().update(db.getSession(), user.setLastSonarlintConnectionDate(sonarLintLastConnectionDate));
+    db.getSession().commit();
+    return user;
+  }
+
   public Optional<UserDto> selectUserByLogin(String login) {
     return Optional.ofNullable(dbClient.userDao().selectByLogin(db.getSession(), login));
   }
index 6d3bb32da1bb81e4e707299dc7e753c3fca0af9c..7ce5deac9906615230c891a6518c954a938eb1f2 100644 (file)
  */
 package org.sonar.server.user.ws;
 
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
 import java.util.Set;
 import java.util.stream.IntStream;
 import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.api.server.ws.WebService.Param;
+import org.sonar.api.utils.DateUtils;
 import org.sonar.core.util.UuidFactory;
 import org.sonar.db.DbTester;
 import org.sonar.db.scim.ScimUserDao;
@@ -452,11 +455,15 @@ public class SearchActionIT {
       .setScmAccounts(emptyList())
       .setExternalLogin("fmallet")
       .setExternalIdentityProvider("sonarqube"));
+    long lastConnection = DateUtils.parseOffsetDateTime("2019-03-27T09:51:50+0100").toInstant().toEpochMilli();
+    fmallet = db.users().updateLastConnectionDate(fmallet, lastConnection);
+    fmallet = db.users().updateSonarLintLastConnectionDate(fmallet, lastConnection);
     UserDto simon = db.users().insertUser(u -> u.setLogin("sbrandhof").setName("Simon").setEmail("s.brandhof@company.tld")
       .setLocal(false)
       .setExternalLogin("sbrandhof@ldap.com")
       .setExternalIdentityProvider("sonarqube")
       .setScmAccounts(asList("simon.brandhof", "s.brandhof@company.tld")));
+
     mockUsersAsManaged(simon.getUuid());
 
     GroupDto sonarUsers = db.users().insertGroup("sonar-users");
@@ -481,7 +488,54 @@ public class SearchActionIT {
     assertThat(action).isNotNull();
     assertThat(action.isPost()).isFalse();
     assertThat(action.responseExampleAsString()).isNotEmpty();
-    assertThat(action.params()).hasSize(5);
+    assertThat(action.params()).hasSize(9);
+  }
+
+  @Test
+  public void search_whenFilteringConnectionDate_shouldApplyFilter() {
+    userSession.logIn().setSystemAdministrator();
+    final Instant lastConnection = Instant.now();
+    UserDto user = db.users().insertUser(u -> u
+      .setLogin("user-%_%-login")
+      .setName("user-name")
+      .setEmail("user@mail.com")
+      .setLocal(true)
+      .setScmAccounts(singletonList("user1")));
+    user = db.users().updateLastConnectionDate(user, lastConnection.toEpochMilli());
+    user = db.users().updateSonarLintLastConnectionDate(user, lastConnection.toEpochMilli());
+
+    assertThat(ws.newRequest()
+      .setParam("q", "user-%_%-")
+      .executeProtobuf(SearchWsResponse.class).getUsersList())
+      .extracting(User::getLogin)
+      .containsExactlyInAnyOrder(user.getLogin());
+
+    assertUserWithFilter("lastConnectedAfter", lastConnection.minus(1, ChronoUnit.DAYS), user.getLogin(), true);
+    assertUserWithFilter("lastConnectedAfter", lastConnection.plus(1, ChronoUnit.DAYS), user.getLogin(), false);
+    assertUserWithFilter("lastConnectedBefore", lastConnection.minus(1, ChronoUnit.DAYS), user.getLogin(), false);
+    assertUserWithFilter("lastConnectedBefore", lastConnection.plus(1, ChronoUnit.DAYS), user.getLogin(), true);
+
+    assertUserWithFilter("slLastConnectedAfter", lastConnection.minus(1, ChronoUnit.DAYS), user.getLogin(), true);
+    assertUserWithFilter("slLastConnectedAfter", lastConnection.plus(1, ChronoUnit.DAYS), user.getLogin(), false);
+    assertUserWithFilter("slLastConnectedBefore", lastConnection.minus(1, ChronoUnit.DAYS), user.getLogin(), false);
+    assertUserWithFilter("slLastConnectedBefore", lastConnection.plus(1, ChronoUnit.DAYS), user.getLogin(), true);
+
+    assertUserWithFilter("slLastConnectedAfter", lastConnection, user.getLogin(), true);
+    assertUserWithFilter("slLastConnectedBefore", lastConnection, user.getLogin(), true);
+  }
+
+  private void assertUserWithFilter(String field, Instant filterValue, String userLogin, boolean isExpectedToBeThere) {
+    var assertion = assertThat(ws.newRequest()
+      .setParam("q", "user-%_%-")
+      .setParam(field, DateUtils.formatDateTime(filterValue.toEpochMilli()))
+      .executeProtobuf(SearchWsResponse.class).getUsersList());
+    if (isExpectedToBeThere) {
+      assertion
+        .extracting(User::getLogin)
+        .containsExactlyInAnyOrder(userLogin);
+    } else {
+      assertion.isEmpty();
+    }
   }
 
   private void mockUsersAsManaged(String... userUuids) {
index 51a9561443e03616a9a0df7d47580c0f96a2fc0a..358efd5315eb4825d7b3cc4cd72969e9a50b029b 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.server.user.ws;
 
 import com.google.common.collect.Multimap;
+import java.time.OffsetDateTime;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -33,6 +34,8 @@ import org.sonar.api.server.ws.Change;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.MessageException;
 import org.sonar.api.utils.Paging;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
@@ -40,6 +43,7 @@ import org.sonar.db.user.UserDto;
 import org.sonar.db.user.UserQuery;
 import org.sonar.server.es.SearchOptions;
 import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.exceptions.ServerException;
 import org.sonar.server.issue.AvatarResolver;
 import org.sonar.server.management.ManagedInstanceService;
 import org.sonar.server.user.UserSession;
@@ -49,13 +53,12 @@ import org.sonarqube.ws.Users.SearchWsResponse;
 import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Strings.emptyToNull;
-import static java.util.Comparator.comparing;
 import static java.lang.Boolean.TRUE;
+import static java.util.Comparator.comparing;
 import static java.util.Optional.ofNullable;
 import static org.sonar.api.server.ws.WebService.Param.PAGE;
 import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE;
 import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY;
-import static org.sonar.api.utils.DateUtils.formatDateTime;
 import static org.sonar.api.utils.Paging.forPageIndex;
 import static org.sonar.core.util.stream.MoreCollectors.toList;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
@@ -67,8 +70,13 @@ import static org.sonarqube.ws.Users.SearchWsResponse.newBuilder;
 public class SearchAction implements UsersWsAction {
   private static final String DEACTIVATED_PARAM = "deactivated";
   private static final String MANAGED_PARAM = "managed";
-  private static final int MAX_PAGE_SIZE = 500;
 
+
+  private static final int MAX_PAGE_SIZE = 500;
+  private static final String LAST_CONNECTION_DATE_FROM = "lastConnectedAfter";
+  private static final String LAST_CONNECTION_DATE_TO = "lastConnectedBefore";
+  private static final String SONAR_LINT_LAST_CONNECTION_DATE_FROM = "slLastConnectedAfter";
+  private static final String SONAR_LINT_LAST_CONNECTION_DATE_TO = "slLastConnectedBefore";
   private final UserSession userSession;
   private final DbClient dbClient;
   private final AvatarResolver avatarResolver;
@@ -93,11 +101,17 @@ public class SearchAction implements UsersWsAction {
         "   <li>'externalProvider'</li>" +
         "   <li>'groups'</li>" +
         "   <li>'lastConnectionDate'</li>" +
+        "   <li>'sonarLintLastConnectionDate'</li>" +
         "   <li>'tokensCount'</li>" +
         "</ul>" +
         "Field 'lastConnectionDate' is only updated every hour, so it may not be accurate, for instance when a user authenticates many times in less than one hour.")
       .setSince("3.6")
       .setChangelog(
+        new Change("10.1", "New optional parameters " + SONAR_LINT_LAST_CONNECTION_DATE_FROM +
+          " and " + SONAR_LINT_LAST_CONNECTION_DATE_TO + " to filter users by SonarLint last connection date"),
+        new Change("10.1", "New optional parameters " + LAST_CONNECTION_DATE_FROM +
+          " and " + LAST_CONNECTION_DATE_TO + " to filter users by SonarQube last connection date"),
+        new Change("10.1", "New field 'sonarLintLastConnectionDate' is added to response"),
         new Change("10.0", "'q' parameter values is now always performing a case insensitive match"),
         new Change("10.0", "New parameter 'managed' to optionally search by managed status"),
         new Change("10.0", "Response includes 'managed' field."),
@@ -112,6 +126,7 @@ public class SearchAction implements UsersWsAction {
 
     action.addPagingParams(50, SearchOptions.MAX_PAGE_SIZE);
 
+    final String dateExample = "2020-01-01T00:00:00+0100";
     action.createParam(TEXT_QUERY)
       .setMinimumLength(2)
       .setDescription("Filter on login, name and email.<br />" +
@@ -128,6 +143,38 @@ public class SearchAction implements UsersWsAction {
       .setRequired(false)
       .setDefaultValue(null)
       .setBooleanPossibleValues();
+    action.createParam(LAST_CONNECTION_DATE_FROM)
+      .setSince("10.1")
+      .setDescription("""
+        Filter the users based on the last connection date field. Only users who interacted with this instance at or after the date will be returned.
+        The format must be ISO 8601 datetime format (YYYY-MM-DDThh:mm:ss±hhmm)""")
+      .setRequired(false)
+      .setDefaultValue(null)
+      .setExampleValue(dateExample);
+    action.createParam(LAST_CONNECTION_DATE_TO)
+      .setSince("10.1")
+      .setDescription("""
+        Filter the users based on the last connection date field. Only users who interacted with this instance at or before the date will be returned.
+        The format must be ISO 8601 datetime format (YYYY-MM-DDThh:mm:ss±hhmm)""")
+      .setRequired(false)
+      .setDefaultValue(null)
+      .setExampleValue(dateExample);
+    action.createParam(SONAR_LINT_LAST_CONNECTION_DATE_FROM)
+      .setSince("10.1")
+      .setDescription("""
+        Filter the users based on the sonar lint last connection date field. Only users who interacted with this instance using SonarLint at or after the date will be returned.
+        The format must be ISO 8601 datetime format (YYYY-MM-DDThh:mm:ss±hhmm)""")
+      .setRequired(false)
+      .setDefaultValue(null)
+      .setExampleValue(dateExample);
+    action.createParam(SONAR_LINT_LAST_CONNECTION_DATE_TO)
+      .setSince("10.1")
+      .setDescription("""
+        Filter the users based on the sonar lint last connection date field. Only users who interacted with this instance using SonarLint at or before the date will be returned.
+        The format must be ISO 8601 datetime format (YYYY-MM-DDThh:mm:ss±hhmm)""")
+      .setRequired(false)
+      .setDefaultValue(null)
+      .setExampleValue(dateExample);
   }
 
   @Override
@@ -156,20 +203,22 @@ public class SearchAction implements UsersWsAction {
   }
 
   private UserQuery buildUserQuery(SearchRequest request) {
+    UserQuery.UserQueryBuilder builder = UserQuery.builder();
+    request.getLastConnectionDateFrom().ifPresent(builder::lastConnectionDateFrom);
+    request.getLastConnectionDateTo().ifPresent(builder::lastConnectionDateTo);
+    request.getSonarLintLastConnectionDateFrom().ifPresent(builder::sonarLintLastConnectionDateFrom);
+    request.getSonarLintLastConnectionDateTo().ifPresent(builder::sonarLintLastConnectionDateTo);
+
     if (managedInstanceService.isInstanceExternallyManaged()) {
       String managedInstanceSql = Optional.ofNullable(request.isManaged())
         .map(managedInstanceService::getManagedUsersSqlFilter)
         .orElse(null);
-      return UserQuery.builder()
-        .isActive(!request.isDeactivated())
-        .searchText(request.getQuery())
-        .isManagedClause(managedInstanceSql)
-        .build();
-    }
-    if (request.isManaged() != null) {
+      builder.isManagedClause(managedInstanceSql);
+    } else if (request.isManaged() != null) {
       throw BadRequestException.create("The 'managed' parameter is only available for managed instances.");
     }
-    return UserQuery.builder()
+
+    return builder
       .isActive(!request.isDeactivated())
       .searchText(request.getQuery())
       .build();
@@ -215,7 +264,9 @@ public class SearchAction implements UsersWsAction {
       }
       ofNullable(user.getExternalLogin()).ifPresent(userBuilder::setExternalIdentity);
       ofNullable(tokensCount).ifPresent(userBuilder::setTokensCount);
-      ofNullable(user.getLastConnectionDate()).ifPresent(date -> userBuilder.setLastConnectionDate(formatDateTime(date)));
+      ofNullable(user.getLastConnectionDate()).map(DateUtils::formatDateTime).ifPresent(userBuilder::setLastConnectionDate);
+      ofNullable(user.getLastSonarlintConnectionDate())
+        .map(DateUtils::formatDateTime).ifPresent(userBuilder::setSonarLintLastConnectionDate);
       userBuilder.setManaged(TRUE.equals(managed));
     }
     return userBuilder.build();
@@ -228,6 +279,10 @@ public class SearchAction implements UsersWsAction {
       .setQuery(request.param(TEXT_QUERY))
       .setDeactivated(request.mandatoryParamAsBoolean(DEACTIVATED_PARAM))
       .setManaged(request.paramAsBoolean(MANAGED_PARAM))
+      .setLastConnectionDateFrom(request.param(LAST_CONNECTION_DATE_FROM))
+      .setLastConnectionDateTo(request.param(LAST_CONNECTION_DATE_TO))
+      .setSonarLintLastConnectionDateFrom(request.param(SONAR_LINT_LAST_CONNECTION_DATE_FROM))
+      .setSonarLintLastConnectionDateTo(request.param(SONAR_LINT_LAST_CONNECTION_DATE_TO))
       .setPage(request.mandatoryParamAsInt(PAGE))
       .setPageSize(pageSize)
       .build();
@@ -239,6 +294,10 @@ public class SearchAction implements UsersWsAction {
     private final String query;
     private final boolean deactivated;
     private final Boolean managed;
+    private final OffsetDateTime lastConnectionDateFrom;
+    private final OffsetDateTime lastConnectionDateTo;
+    private final OffsetDateTime sonarLintLastConnectionDateFrom;
+    private final OffsetDateTime sonarLintLastConnectionDateTo;
 
     private SearchRequest(Builder builder) {
       this.page = builder.page;
@@ -246,6 +305,14 @@ public class SearchAction implements UsersWsAction {
       this.query = builder.query;
       this.deactivated = builder.deactivated;
       this.managed = builder.managed;
+      try {
+        this.lastConnectionDateFrom = Optional.ofNullable(builder.lastConnectionDateFrom).map(DateUtils::parseOffsetDateTime).orElse(null);
+        this.lastConnectionDateTo = Optional.ofNullable(builder.lastConnectionDateTo).map(DateUtils::parseOffsetDateTime).orElse(null);
+        this.sonarLintLastConnectionDateFrom = Optional.ofNullable(builder.sonarLintLastConnectionDateFrom).map(DateUtils::parseOffsetDateTime).orElse(null);
+        this.sonarLintLastConnectionDateTo = Optional.ofNullable(builder.sonarLintLastConnectionDateTo).map(DateUtils::parseOffsetDateTime).orElse(null);
+      } catch (MessageException me) {
+        throw new ServerException(400, me.getMessage());
+      }
     }
 
     public Integer getPage() {
@@ -270,6 +337,22 @@ public class SearchAction implements UsersWsAction {
       return managed;
     }
 
+    public Optional<OffsetDateTime> getLastConnectionDateFrom() {
+      return Optional.ofNullable(lastConnectionDateFrom);
+    }
+
+    public Optional<OffsetDateTime> getLastConnectionDateTo() {
+      return Optional.ofNullable(lastConnectionDateTo);
+    }
+
+    public Optional<OffsetDateTime> getSonarLintLastConnectionDateFrom() {
+      return Optional.ofNullable(sonarLintLastConnectionDateFrom);
+    }
+
+    public Optional<OffsetDateTime> getSonarLintLastConnectionDateTo() {
+      return Optional.ofNullable(sonarLintLastConnectionDateTo);
+    }
+
     public static Builder builder() {
       return new Builder();
     }
@@ -281,6 +364,11 @@ public class SearchAction implements UsersWsAction {
     private String query;
     private boolean deactivated;
     private Boolean managed;
+    private String lastConnectionDateFrom;
+    private String lastConnectionDateTo;
+    private String sonarLintLastConnectionDateFrom;
+    private String sonarLintLastConnectionDateTo;
+
 
     private Builder() {
       // enforce factory method use
@@ -311,6 +399,26 @@ public class SearchAction implements UsersWsAction {
       return this;
     }
 
+    public Builder setLastConnectionDateFrom(@Nullable String lastConnectionDateFrom) {
+      this.lastConnectionDateFrom = lastConnectionDateFrom;
+      return this;
+    }
+
+    public Builder setLastConnectionDateTo(@Nullable String lastConnectionDateTo) {
+      this.lastConnectionDateTo = lastConnectionDateTo;
+      return this;
+    }
+
+    public Builder setSonarLintLastConnectionDateFrom(@Nullable String sonarLintLastConnectionDateFrom) {
+      this.sonarLintLastConnectionDateFrom = sonarLintLastConnectionDateFrom;
+      return this;
+    }
+
+    public Builder setSonarLintLastConnectionDateTo(@Nullable String sonarLintLastConnectionDateTo) {
+      this.sonarLintLastConnectionDateTo = sonarLintLastConnectionDateTo;
+      return this;
+    }
+
     public SearchRequest build() {
       return new SearchRequest(this);
     }
index 84e141b731f35b79eaf0011dc3e081fc52d082c5..110b0fb8377fa064eba150b5671a2d84d6a604c0 100644 (file)
@@ -19,7 +19,9 @@
       "externalIdentity": "fmallet",
       "externalProvider": "sonarqube",
       "avatar": "2f9dff586d3f74f825b059e3798a3bbb",
-      "managed": false
+      "lastConnectionDate": "2019-03-27T09:51:50+0100",
+      "managed": false,
+      "sonarLintLastConnectionDate": "2019-03-27T09:51:50+0100"
     },
     {
       "login": "sbrandhof",
index d8ba5a43868b78a80f91e42f30246f3838476adb..ed8742bed9f6a11daee05d5f59283216aff3269e 100644 (file)
@@ -45,6 +45,7 @@ message SearchWsResponse {
     optional string avatar = 11;
     optional string lastConnectionDate = 12;
     optional bool managed = 13;
+    optional string sonarLintLastConnectionDate = 14;
   }
 
   message Groups {