Add API portion of account status

Adds PutStatus.java and GetStatus.java for modification of the logged-in
user's status field.

Feature: Issue 4394
Change-Id: Id13241dcd98c46101cea2f2337c1fb731d0465a0
This commit is contained in:
Kasper Nilsson
2017-01-27 13:51:07 -08:00
parent f3837bd5df
commit eb64a0ae1e
12 changed files with 239 additions and 1 deletions

View File

@@ -285,6 +285,66 @@ Deletes the name of an account.
HTTP/1.1 204 No Content HTTP/1.1 204 No Content
---- ----
[[get-account-status]]
=== Get Account Status
--
'GET /accounts/link:#account-id[\{account-id\}]/status'
--
Retrieves the status of an account.
.Request
----
GET /accounts/self/status HTTP/1.0
----
.Response
----
HTTP/1.1 200 OK
Content-Disposition: attachment
Content-Type: application/json; charset=UTF-8
)]}'
"Available"
----
If the account does not have a status an empty string is returned.
[[set-account-status]]
=== Set Account Status
--
'PUT /accounts/link:#account-id[\{account-id\}]/status'
--
Sets the status of an account.
The new account status must be provided in the request body inside
an link:#account-status-input[AccountStatusInput] entity.
.Request
----
PUT /accounts/self/status HTTP/1.0
Content-Type: application/json; charset=UTF-8
{
"status": "Out Of Office"
}
----
As response the new account status is returned.
.Response
----
HTTP/1.1 200 OK
Content-Disposition: attachment
Content-Type: application/json; charset=UTF-8
)]}'
"Out Of Office"
----
If the name was deleted the response is "`204 No Content`".
[[get-username]] [[get-username]]
=== Get Username === Get Username
-- --
@@ -2173,6 +2233,18 @@ for an account.
If not set or if set to an empty string, the account name is deleted. If not set or if set to an empty string, the account name is deleted.
|============================= |=============================
[[account-status-input]]
=== AccountStatusInput
The `AccountStatusInput` entity contains information for setting a status
for an account.
[options="header",cols="1,^2,4"]
|=============================
|Field Name ||Description
|`status` |optional|The new status of the account. +
If not set or if set to an empty string, the account status is deleted.
|=============================
[[capability-info]] [[capability-info]]
=== CapabilityInfo === CapabilityInfo
The `CapabilityInfo` entity contains information about the global The `CapabilityInfo` entity contains information about the global

View File

@@ -51,6 +51,7 @@ public class TestAccount {
public final String fullName; public final String fullName;
public final KeyPair sshKey; public final KeyPair sshKey;
public final String httpPassword; public final String httpPassword;
public String status;
TestAccount(Account.Id id, String username, String email, String fullName, TestAccount(Account.Id id, String username, String email, String fullName,
KeyPair sshKey, String httpPassword) { KeyPair sshKey, String httpPassword) {

View File

@@ -421,6 +421,19 @@ public class AccountIT extends AbstractDaemonTest {
} }
} }
@Test
public void putStatus() throws Exception {
List<String> statuses = ImmutableList.of(
"OOO", "Busy");
AccountInfo info;
for (String status : statuses) {
gApi.accounts().self().setStatus(status);
admin.status = status;
info = gApi.accounts().self().get();
assertUser(info, admin);
}
}
@Test @Test
public void addInvalidEmail() throws Exception { public void addInvalidEmail() throws Exception {
List<String> emails = ImmutableList.of( List<String> emails = ImmutableList.of(
@@ -877,5 +890,6 @@ public class AccountIT extends AbstractDaemonTest {
assertThat(info.name).isEqualTo(account.fullName); assertThat(info.name).isEqualTo(account.fullName);
assertThat(info.email).isEqualTo(account.email); assertThat(info.email).isEqualTo(account.email);
assertThat(info.username).isEqualTo(account.username); assertThat(info.username).isEqualTo(account.username);
assertThat(info.status).isEqualTo(account.status);
} }
} }

View File

@@ -66,6 +66,8 @@ public interface AccountApi {
void addEmail(EmailInput input) throws RestApiException; void addEmail(EmailInput input) throws RestApiException;
void setStatus(String status) throws RestApiException;
List<SshKeyInfo> listSshKeys() throws RestApiException; List<SshKeyInfo> listSshKeys() throws RestApiException;
SshKeyInfo addSshKey(String key) throws RestApiException; SshKeyInfo addSshKey(String key) throws RestApiException;
void deleteSshKey(int seq) throws RestApiException; void deleteSshKey(int seq) throws RestApiException;
@@ -184,6 +186,11 @@ public interface AccountApi {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@Override
public void setStatus(String status) {
throw new NotImplementedException();
}
@Override @Override
public List<SshKeyInfo> listSshKeys() { public List<SshKeyInfo> listSshKeys() {
throw new NotImplementedException(); throw new NotImplementedException();

View File

@@ -24,6 +24,7 @@ public class AccountInfo {
public String username; public String username;
public List<AvatarInfo> avatars; public List<AvatarInfo> avatars;
public Boolean _moreAccounts; public Boolean _moreAccounts;
public String status;
public AccountInfo(Integer id) { public AccountInfo(Integer id) {
this._accountId = id; this._accountId = id;

View File

@@ -42,7 +42,10 @@ public abstract class AccountDirectory {
USERNAME, USERNAME,
/** Numeric account ID, may be deprecated. */ /** Numeric account ID, may be deprecated. */
ID ID,
/** The user-settable status of this account (e.g. busy, OOO, available) */
STATUS
} }
public abstract void fillAccountInfo( public abstract void fillAccountInfo(

View File

@@ -42,6 +42,7 @@ public class AccountLoader {
FillOptions.NAME, FillOptions.NAME,
FillOptions.EMAIL, FillOptions.EMAIL,
FillOptions.USERNAME, FillOptions.USERNAME,
FillOptions.STATUS,
FillOptions.AVATARS)); FillOptions.AVATARS));
public interface Factory { public interface Factory {

View File

@@ -0,0 +1,27 @@
// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.account;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.inject.Singleton;
@Singleton
public class GetStatus implements RestReadView<AccountResource> {
@Override
public String apply(AccountResource rsrc) {
return Strings.nullToEmpty(rsrc.getUser().getAccount().getStatus());
}
}

View File

@@ -103,6 +103,11 @@ public class InternalAccountDirectory extends AccountDirectory {
? AccountState.getUserName(externalIds) ? AccountState.getUserName(externalIds)
: null; : null;
} }
if (options.contains(FillOptions.STATUS)) {
info.status = account.getStatus();
}
if (options.contains(FillOptions.AVATARS)) { if (options.contains(FillOptions.AVATARS)) {
AvatarProvider ap = avatar.get(); AvatarProvider ap = avatar.get();
if (ap != null) { if (ap != null) {

View File

@@ -44,6 +44,8 @@ public class Module extends RestApiModule {
get(ACCOUNT_KIND, "name").to(GetName.class); get(ACCOUNT_KIND, "name").to(GetName.class);
put(ACCOUNT_KIND, "name").to(PutName.class); put(ACCOUNT_KIND, "name").to(PutName.class);
delete(ACCOUNT_KIND, "name").to(PutName.class); delete(ACCOUNT_KIND, "name").to(PutName.class);
get(ACCOUNT_KIND, "status").to(GetStatus.class);
put(ACCOUNT_KIND, "status").to(PutStatus.class);
get(ACCOUNT_KIND, "username").to(GetUsername.class); get(ACCOUNT_KIND, "username").to(GetUsername.class);
put(ACCOUNT_KIND, "username").to(PutUsername.class); put(ACCOUNT_KIND, "username").to(PutUsername.class);
get(ACCOUNT_KIND, "active").to(GetActive.class); get(ACCOUNT_KIND, "active").to(GetActive.class);

View File

@@ -0,0 +1,91 @@
// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.account;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.PutStatus.Input;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.Collections;
@Singleton
public class PutStatus implements RestModifyView<AccountResource, Input> {
public static class Input {
@DefaultInput
String status;
public Input(String status) {
this.status = status;
}
public Input() {
}
}
private final Provider<CurrentUser> self;
private final Provider<ReviewDb> dbProvider;
private final AccountCache byIdCache;
@Inject
PutStatus(Provider<CurrentUser> self, Provider<ReviewDb> dbProvider,
AccountCache byIdCache) {
this.self = self;
this.dbProvider = dbProvider;
this.byIdCache = byIdCache;
}
@Override
public Response<String> apply(AccountResource rsrc, Input input)
throws AuthException,
ResourceNotFoundException, OrmException, IOException {
if (self.get() != rsrc.getUser()
&& !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to set status");
}
return apply(rsrc.getUser(), input);
}
public Response<String> apply(IdentifiedUser user, Input input)
throws ResourceNotFoundException, OrmException,
IOException {
if (input == null) {
input = new Input();
}
Account a = dbProvider.get().accounts().get(user.getAccountId());
if (a == null) {
throw new ResourceNotFoundException("account not found");
}
a.setStatus(Strings.nullToEmpty(input.status));
dbProvider.get().accounts().update(Collections.singleton(a));
byIdCache.evict(a.getId());
return Strings.isNullOrEmpty(a.getStatus())
? Response.none()
: Response.ok(a.getStatus());
}
}

View File

@@ -59,6 +59,7 @@ import com.google.gerrit.server.account.Index;
import com.google.gerrit.server.account.PostWatchedProjects; import com.google.gerrit.server.account.PostWatchedProjects;
import com.google.gerrit.server.account.PutActive; import com.google.gerrit.server.account.PutActive;
import com.google.gerrit.server.account.PutAgreement; import com.google.gerrit.server.account.PutAgreement;
import com.google.gerrit.server.account.PutStatus;
import com.google.gerrit.server.account.SetDiffPreferences; import com.google.gerrit.server.account.SetDiffPreferences;
import com.google.gerrit.server.account.SetEditPreferences; import com.google.gerrit.server.account.SetEditPreferences;
import com.google.gerrit.server.account.SetPreferences; import com.google.gerrit.server.account.SetPreferences;
@@ -115,6 +116,7 @@ public class AccountApiImpl implements AccountApi {
private final Index index; private final Index index;
private final GetExternalIds getExternalIds; private final GetExternalIds getExternalIds;
private final DeleteExternalIds deleteExternalIds; private final DeleteExternalIds deleteExternalIds;
private final PutStatus putStatus;
@Inject @Inject
AccountApiImpl(AccountLoader.Factory ailf, AccountApiImpl(AccountLoader.Factory ailf,
@@ -148,6 +150,7 @@ public class AccountApiImpl implements AccountApi {
Index index, Index index,
GetExternalIds getExternalIds, GetExternalIds getExternalIds,
DeleteExternalIds deleteExternalIds, DeleteExternalIds deleteExternalIds,
PutStatus putStatus,
@Assisted AccountResource account) { @Assisted AccountResource account) {
this.account = account; this.account = account;
this.accountLoaderFactory = ailf; this.accountLoaderFactory = ailf;
@@ -181,6 +184,7 @@ public class AccountApiImpl implements AccountApi {
this.index = index; this.index = index;
this.getExternalIds = getExternalIds; this.getExternalIds = getExternalIds;
this.deleteExternalIds = deleteExternalIds; this.deleteExternalIds = deleteExternalIds;
this.putStatus = putStatus;
} }
@Override @Override
@@ -373,6 +377,16 @@ public class AccountApiImpl implements AccountApi {
} }
} }
@Override
public void setStatus(String status) throws RestApiException {
PutStatus.Input in = new PutStatus.Input(status);
try {
putStatus.apply(account, in);
} catch (OrmException | IOException e) {
throw new RestApiException("Cannot set status", e);
}
}
@Override @Override
public List<SshKeyInfo> listSshKeys() throws RestApiException { public List<SshKeyInfo> listSshKeys() throws RestApiException {
try { try {