Added DELETE/POST endpoint for watched projects

Added DELETE and POST endpoints for removing,
adding and updating watched projects. This is an
effort towards migrating to PolyGerrit UI.

Change-Id: I6203af9ed0fd6448fc1395512bc3ffdca93c5c18
This commit is contained in:
Patrick Hiesel
2016-04-28 10:51:47 +02:00
committed by Edwin Kempin
parent e56cf3af23
commit bed6d98869
8 changed files with 514 additions and 0 deletions

View File

@@ -1478,6 +1478,101 @@ The result is sorted by project name in ascending order.
]
----
[[set-watched-projects]]
=== Add/Update a List of Watched Project Entities
--
'POST /accounts/link:#account-id[\{account-id\}]/watched.projects'
--
Add new projects to watch or update existing watched projects.
Projects that are already watched by a user will be updated with
the provided configuration. All other projects in the request
will be watched using the provided configuration. The posted body
can contain link:#project-watch-info[ProjectWatchInfo] entities.
Omitted boolean values will be set to false.
.Request
----
POST /a/accounts/self/watched.projects HTTP/1.0
Content-Type: application/json;charset=UTF-8
[
{
"project": "Test Project 1",
"notify_new_changes": true,
"notify_new_patch_sets": true,
"notify_all_comments": true,
}
]
----
As result the watched projects of the user are returned as a list of
link:#project-watch-info[ProjectWatchInfo] entities.
The result is sorted by project name in ascending order.
.Response
----
HTTP/1.1 200 OK
Content-Disposition: attachment
Content-Type: application/json; charset=UTF-8
)]}'
[
{
"project": "Test Project 1",
"notify_new_changes": true,
"notify_new_patch_sets": true,
"notify_all_comments": true,
},
{
"project": "Test Project 2",
"notify_new_changes": true,
"notify_new_patch_sets": true,
"notify_all_comments": true,
}
]
----
[[delete-watched-projects]]
=== Delete Watched Projects
--
'POST /accounts/link:#account-id[\{account-id\}]/watched.projects:delete'
--
Projects posted to this endpoint will no longer be watched. The posted body
can contain an array of project names as strings.
.Request
----
POST /a/accounts/self/watched.projects:delete HTTP/1.0
Content-Type: application/json;charset=UTF-8
[
"Test Project 1"
]
----
As result the watched projects of the user are returned as a list of
link:#project-watch-info[ProjectWatchInfo] entities.
The result is sorted by project name in ascending order.
.Response
----
HTTP/1.1 200 OK
Content-Disposition: attachment
Content-Type: application/json; charset=UTF-8
)]}'
[
{
"project": "Test Project 2",
"notify_new_changes": true,
"notify_new_patch_sets": true,
"notify_all_comments": true,
}
]
----
[[get-starred-changes]]
=== Get Starred Changes
--

View File

@@ -0,0 +1,169 @@
// Copyright (C) 2016 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.acceptance.rest.account;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.Lists;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.extensions.client.ProjectWatchInfo;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import org.junit.Test;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class WatchedProjectsIT extends AbstractDaemonTest {
private static final String NEW_PROJECT_NAME = "newProjectAccess";
@Test
public void setAndGetWatchedProjects() throws Exception {
String projectName1 = createProject(NEW_PROJECT_NAME).get();
String projectName2 = createProject(NEW_PROJECT_NAME + "2").get();
List<ProjectWatchInfo> projectsToWatch = new ArrayList<>(2);
ProjectWatchInfo pwi = new ProjectWatchInfo();
pwi.project = projectName1;
pwi.notifyAbandonedChanges = true;
pwi.notifyNewChanges = true;
pwi.notifyAllComments = true;
projectsToWatch.add(pwi);
pwi = new ProjectWatchInfo();
pwi.project = projectName2;
pwi.filter = "branch:master";
pwi.notifySubmittedChanges = true;
pwi.notifyNewPatchSets = true;
projectsToWatch.add(pwi);
List<ProjectWatchInfo> persistedWatchedProjects =
gApi.accounts().self().setWatchedProjects(projectsToWatch);
assertThat(persistedWatchedProjects)
.containsAllIn(projectsToWatch).inOrder();
}
@Test
public void setAndDeleteWatchedProjects() throws Exception {
String projectName1 = createProject(NEW_PROJECT_NAME).get();
String projectName2 = createProject(NEW_PROJECT_NAME + "2").get();
List<ProjectWatchInfo> projectsToWatch = new LinkedList<>();
ProjectWatchInfo pwi = new ProjectWatchInfo();
pwi.project = projectName1;
pwi.notifyAbandonedChanges = true;
pwi.notifyNewChanges = true;
pwi.notifyAllComments = true;
projectsToWatch.add(pwi);
pwi = new ProjectWatchInfo();
pwi.project = projectName2;
pwi.filter = "branch:master";
pwi.notifySubmittedChanges = true;
pwi.notifyNewPatchSets = true;
projectsToWatch.add(pwi);
// Persist watched projects
gApi.accounts().self().setWatchedProjects(projectsToWatch);
List<String> d = Lists.newArrayList(projectName2);
gApi.accounts().self().deleteWatchedProjects(d);
projectsToWatch.remove(pwi);
List<ProjectWatchInfo> persistedWatchedProjects =
gApi.accounts().self().getWatchedProjects();
assertThat(persistedWatchedProjects).doesNotContain(pwi);
assertThat(persistedWatchedProjects).containsAllIn(projectsToWatch);
}
@Test
public void watchNonExistingProject() throws Exception {
String projectName = NEW_PROJECT_NAME + "3";
List<ProjectWatchInfo> projectsToWatch = new ArrayList<>(2);
ProjectWatchInfo pwi = new ProjectWatchInfo();
pwi.project = projectName;
pwi.notifyAbandonedChanges = true;
pwi.notifyNewChanges = true;
pwi.notifyAllComments = true;
projectsToWatch.add(pwi);
exception.expect(UnprocessableEntityException.class);
gApi.accounts().self().setWatchedProjects(projectsToWatch);
}
@Test
public void deleteNonExistingProject() throws Exception {
String projectName = project.get();
// Let another user watch a project
setApiUser(admin);
List<ProjectWatchInfo> projectsToWatch = new LinkedList<>();
ProjectWatchInfo pwi = new ProjectWatchInfo();
pwi.project = projectName;
pwi.notifyAbandonedChanges = true;
pwi.notifyNewChanges = true;
pwi.notifyAllComments = true;
projectsToWatch.add(pwi);
gApi.accounts().self().setWatchedProjects(projectsToWatch);
// Try to delete a watched project using a different user
List<String> d = Lists.newArrayList(projectName);
gApi.accounts().self().deleteWatchedProjects(d);
setApiUser(user);
exception.expect(UnprocessableEntityException.class);
gApi.accounts().self().deleteWatchedProjects(d);
}
@Test
public void modifyProjectWatchUsingOmittedValues() throws Exception {
String projectName = project.get();
// Let another user watch a project
setApiUser(admin);
List<ProjectWatchInfo> projectsToWatch = new LinkedList<>();
ProjectWatchInfo pwi = new ProjectWatchInfo();
pwi.project = projectName;
pwi.notifyAbandonedChanges = true;
pwi.notifyNewChanges = true;
pwi.notifyAllComments = true;
projectsToWatch.add(pwi);
// Persist a defined state
gApi.accounts().self().setWatchedProjects(projectsToWatch);
// Omit previously set value - will set it to false on the server
// The response will not carry this field then as we omit sending
// false values in JSON
pwi.notifyNewChanges = null;
// Perform update
gApi.accounts().self().setWatchedProjects(projectsToWatch);
List<ProjectWatchInfo> watchedProjects =
gApi.accounts().self().getWatchedProjects();
assertThat(watchedProjects).containsAllIn(projectsToWatch);
}
}

View File

@@ -45,6 +45,10 @@ public interface AccountApi {
throws RestApiException;
List<ProjectWatchInfo> getWatchedProjects() throws RestApiException;
List<ProjectWatchInfo> setWatchedProjects(List<ProjectWatchInfo> in)
throws RestApiException;
void deleteWatchedProjects(List<String> in)
throws RestApiException;
void starChange(String id) throws RestApiException;
void unstarChange(String id) throws RestApiException;
@@ -113,6 +117,18 @@ public interface AccountApi {
throw new NotImplementedException();
}
@Override
public List<ProjectWatchInfo> setWatchedProjects(
List<ProjectWatchInfo> in) throws RestApiException {
throw new NotImplementedException();
};
@Override
public void deleteWatchedProjects(List<String> in)
throws RestApiException {
throw new NotImplementedException();
}
@Override
public void starChange(String id) throws RestApiException {
throw new NotImplementedException();

View File

@@ -14,6 +14,8 @@
package com.google.gerrit.extensions.client;
import java.util.Objects;
public class ProjectWatchInfo {
public String project;
public String filter;
@@ -23,4 +25,26 @@ public class ProjectWatchInfo {
public Boolean notifyAllComments;
public Boolean notifySubmittedChanges;
public Boolean notifyAbandonedChanges;
@Override
public boolean equals(Object obj) {
if (obj instanceof ProjectWatchInfo) {
ProjectWatchInfo w = (ProjectWatchInfo) obj;
return Objects.equals(project, w.project)
&& Objects.equals(filter, w.filter)
&& Objects.equals(notifyNewChanges, w.notifyNewChanges)
&& Objects.equals(notifyNewPatchSets, w.notifyNewPatchSets)
&& Objects.equals(notifyAllComments, w.notifyAllComments)
&& Objects.equals(notifySubmittedChanges, w.notifySubmittedChanges)
&& Objects.equals(notifyAbandonedChanges, w.notifyAbandonedChanges);
}
return false;
}
@Override
public int hashCode() {
return Objects
.hash(project, filter, notifyNewChanges, notifyNewPatchSets,
notifyAllComments, notifySubmittedChanges, notifyAbandonedChanges);
}
}

View File

@@ -0,0 +1,77 @@
// Copyright (C) 2016 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.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.AccountProjectWatch;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@Singleton
public class DeleteWatchedProjects
implements RestModifyView<AccountResource, List<String>> {
private final Provider<ReviewDb> dbProvider;
private final Provider<IdentifiedUser> self;
@Inject
DeleteWatchedProjects(Provider<ReviewDb> dbProvider,
Provider<IdentifiedUser> self) {
this.dbProvider = dbProvider;
this.self = self;
}
@Override
public Response<?> apply(
AccountResource rsrc, List<String> input)
throws UnprocessableEntityException, OrmException, AuthException {
if (self.get() != rsrc.getUser()) {
throw new AuthException("It is not allowed to edit project watches "
+ "of other users");
}
ResultSet<AccountProjectWatch> watchedProjects =
dbProvider.get().accountProjectWatches()
.byAccount(rsrc.getUser().getAccountId());
HashMap<String, AccountProjectWatch> watchedProjectsMap = new HashMap<>();
for (AccountProjectWatch watchedProject : watchedProjects) {
watchedProjectsMap
.put(watchedProject.getProjectNameKey().get(), watchedProject);
}
if (input != null) {
List<AccountProjectWatch.Key> keysToDelete = new LinkedList<>();
for (String projectKeyToDelete : input) {
if (!watchedProjectsMap.containsKey(projectKeyToDelete))
throw new UnprocessableEntityException(projectKeyToDelete
+ " is not currently watched by this user.");
keysToDelete.add(watchedProjectsMap.get(projectKeyToDelete).getKey());
}
dbProvider.get().accountProjectWatches().deleteKeys(keysToDelete);
}
return Response.none();
}
}

View File

@@ -57,6 +57,9 @@ public class Module extends RestApiModule {
child(ACCOUNT_KIND, "sshkeys").to(SshKeys.class);
post(ACCOUNT_KIND, "sshkeys").to(AddSshKey.class);
get(ACCOUNT_KIND, "watched.projects").to(GetWatchedProjects.class);
post(ACCOUNT_KIND, "watched.projects").to(PostWatchedProjects.class);
post(ACCOUNT_KIND, "watched.projects:delete")
.to(DeleteWatchedProjects.class);
get(SSH_KEY_KIND).to(GetSshKey.class);
delete(SSH_KEY_KIND).to(DeleteSshKey.class);

View File

@@ -0,0 +1,102 @@
// Copyright (C) 2016 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.gerrit.extensions.client.ProjectWatchInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountProjectWatch;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.project.ProjectsCollection;
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.LinkedList;
import java.util.List;
@Singleton
public class PostWatchedProjects
implements RestModifyView<AccountResource, List<ProjectWatchInfo>> {
private final Provider<IdentifiedUser> self;
private GetWatchedProjects getWatchedProjects;
private Provider<ReviewDb> dbProvider;
private ProjectsCollection projectsCollection;
@Inject
public PostWatchedProjects(GetWatchedProjects getWatchedProjects,
Provider<ReviewDb> dbProvider,
ProjectsCollection projectsCollection,
Provider<IdentifiedUser> self) {
this.getWatchedProjects = getWatchedProjects;
this.dbProvider = dbProvider;
this.projectsCollection = projectsCollection;
this.self = self;
}
@Override
public List<ProjectWatchInfo> apply(AccountResource rsrc,
List<ProjectWatchInfo> input)
throws OrmException, RestApiException, IOException {
if (self.get() != rsrc.getUser()) {
throw new AuthException("not allowed to edit project watches");
}
List<AccountProjectWatch> accountProjectWatchList =
getAccountProjectWatchList(input, rsrc.getUser().getAccountId());
dbProvider.get().accountProjectWatches().upsert(accountProjectWatchList);
return getWatchedProjects.apply(rsrc);
}
private List<AccountProjectWatch> getAccountProjectWatchList(
List<ProjectWatchInfo> input, Account.Id accountId)
throws UnprocessableEntityException, BadRequestException, IOException {
List<AccountProjectWatch> watchedProjects = new LinkedList<>();
for (ProjectWatchInfo a : input) {
if (a.project == null) {
throw new BadRequestException("project name must be specified");
}
Project.NameKey projectKey =
projectsCollection.parse(a.project).getNameKey();
AccountProjectWatch.Key key =
new AccountProjectWatch.Key(accountId, projectKey, a.filter);
AccountProjectWatch apw = new AccountProjectWatch(key);
apw.setNotify(AccountProjectWatch.NotifyType.ABANDONED_CHANGES,
toBoolean(a.notifyAbandonedChanges));
apw.setNotify(AccountProjectWatch.NotifyType.ALL_COMMENTS,
toBoolean(a.notifyAllComments));
apw.setNotify(AccountProjectWatch.NotifyType.NEW_CHANGES,
toBoolean(a.notifyNewChanges));
apw.setNotify(AccountProjectWatch.NotifyType.NEW_PATCHSETS,
toBoolean(a.notifyNewPatchSets));
apw.setNotify(AccountProjectWatch.NotifyType.SUBMITTED_CHANGES,
toBoolean(a.notifySubmittedChanges));
watchedProjects.add(apw);
}
return watchedProjects;
}
private boolean toBoolean(Boolean b) {
return b == null ? false : b;
}
}

View File

@@ -41,10 +41,12 @@ import com.google.gerrit.server.account.GetEditPreferences;
import com.google.gerrit.server.account.GetPreferences;
import com.google.gerrit.server.account.GetSshKeys;
import com.google.gerrit.server.account.GetWatchedProjects;
import com.google.gerrit.server.account.DeleteWatchedProjects;
import com.google.gerrit.server.account.SetDiffPreferences;
import com.google.gerrit.server.account.SetEditPreferences;
import com.google.gerrit.server.account.SetPreferences;
import com.google.gerrit.server.account.SshKeys;
import com.google.gerrit.server.account.PostWatchedProjects;
import com.google.gerrit.server.account.StarredChanges;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.ChangesCollection;
@@ -74,6 +76,8 @@ public class AccountApiImpl implements AccountApi {
private final GetEditPreferences getEditPreferences;
private final SetEditPreferences setEditPreferences;
private final GetWatchedProjects getWatchedProjects;
private final PostWatchedProjects postWatchedProjects;
private final DeleteWatchedProjects deleteWatchedProjects;
private final StarredChanges.Create starredChangesCreate;
private final StarredChanges.Delete starredChangesDelete;
private final CreateEmail.Factory createEmailFactory;
@@ -94,6 +98,8 @@ public class AccountApiImpl implements AccountApi {
GetEditPreferences getEditPreferences,
SetEditPreferences setEditPreferences,
GetWatchedProjects getWatchedProjects,
PostWatchedProjects postWatchedProjects,
DeleteWatchedProjects deleteWatchedProjects,
StarredChanges.Create starredChangesCreate,
StarredChanges.Delete starredChangesDelete,
CreateEmail.Factory createEmailFactory,
@@ -114,6 +120,8 @@ public class AccountApiImpl implements AccountApi {
this.getEditPreferences = getEditPreferences;
this.setEditPreferences = setEditPreferences;
this.getWatchedProjects = getWatchedProjects;
this.postWatchedProjects = postWatchedProjects;
this.deleteWatchedProjects = deleteWatchedProjects;
this.starredChangesCreate = starredChangesCreate;
this.starredChangesDelete = starredChangesDelete;
this.createEmailFactory = createEmailFactory;
@@ -205,6 +213,26 @@ public class AccountApiImpl implements AccountApi {
}
}
@Override
public List<ProjectWatchInfo> setWatchedProjects(
List<ProjectWatchInfo> in) throws RestApiException {
try {
return postWatchedProjects.apply(account, in);
} catch (OrmException | IOException e) {
throw new RestApiException("Cannot update watched projects", e);
}
}
@Override
public void deleteWatchedProjects(List<String> in)
throws RestApiException {
try {
deleteWatchedProjects.apply(account, in);
} catch (OrmException e) {
throw new RestApiException("Cannot delete watched projects", e);
}
}
@Override
public void starChange(String id) throws RestApiException {
try {