mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-01 19:15:52 +01:00
Merged 2.0.0-m3
This commit is contained in:
@@ -114,4 +114,5 @@ public interface GenericDAO<T>
|
|||||||
* @return all items
|
* @return all items
|
||||||
*/
|
*/
|
||||||
public Collection<T> getAll();
|
public Collection<T> getAll();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ public interface Manager<T extends ModelObject>
|
|||||||
extends HandlerBase<T>, LastModifiedAware
|
extends HandlerBase<T>, LastModifiedAware
|
||||||
{
|
{
|
||||||
|
|
||||||
|
int DEFAULT_LIMIT = 5;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reloads a object from store and overwrites all changes.
|
* Reloads a object from store and overwrites all changes.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ public class ManagerDecorator<T extends ModelObject> implements Manager<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(T object) throws NotFoundException {
|
public void delete(T object){
|
||||||
decorated.delete(object);
|
decorated.delete(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,12 +82,12 @@ public class ManagerDecorator<T extends ModelObject> implements Manager<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void modify(T object) throws NotFoundException {
|
public void modify(T object){
|
||||||
decorated.modify(object);
|
decorated.modify(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void refresh(T object) throws NotFoundException {
|
public void refresh(T object){
|
||||||
decorated.refresh(object);
|
decorated.refresh(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package sonia.scm;
|
package sonia.scm;
|
||||||
|
|
||||||
public class NotFoundException extends Exception {
|
public class NotFoundException extends RuntimeException {
|
||||||
public NotFoundException(String type, String id) {
|
public NotFoundException(String type, String id) {
|
||||||
super(type + " with id '" + id + "' not found");
|
super(type + " with id '" + id + "' not found");
|
||||||
}
|
}
|
||||||
|
|||||||
15
scm-core/src/main/java/sonia/scm/ReducedModelObject.java
Normal file
15
scm-core/src/main/java/sonia/scm/ReducedModelObject.java
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package sonia.scm;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a reduced form of a model object.
|
||||||
|
* It can be used as search result to avoid returning the whole object properties.
|
||||||
|
*
|
||||||
|
* @author Mohamed Karray
|
||||||
|
*/
|
||||||
|
public interface ReducedModelObject {
|
||||||
|
|
||||||
|
String getId();
|
||||||
|
|
||||||
|
String getDisplayName();
|
||||||
|
}
|
||||||
@@ -42,6 +42,7 @@ import com.google.common.base.Objects;
|
|||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import sonia.scm.BasicPropertiesAware;
|
import sonia.scm.BasicPropertiesAware;
|
||||||
import sonia.scm.ModelObject;
|
import sonia.scm.ModelObject;
|
||||||
|
import sonia.scm.ReducedModelObject;
|
||||||
import sonia.scm.util.Util;
|
import sonia.scm.util.Util;
|
||||||
import sonia.scm.util.ValidationUtil;
|
import sonia.scm.util.ValidationUtil;
|
||||||
|
|
||||||
@@ -60,11 +61,11 @@ import java.util.List;
|
|||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@StaticPermissions(value = "group", globalPermissions = {"create", "list"})
|
@StaticPermissions(value = "group", globalPermissions = {"create", "list", "autocomplete"})
|
||||||
@XmlRootElement(name = "groups")
|
@XmlRootElement(name = "groups")
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public class Group extends BasicPropertiesAware
|
public class Group extends BasicPropertiesAware
|
||||||
implements ModelObject, PermissionObject
|
implements ModelObject, PermissionObject, ReducedModelObject
|
||||||
{
|
{
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
@@ -309,6 +310,11 @@ public class Group extends BasicPropertiesAware
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a timestamp of the last modified date of this group.
|
* Returns a timestamp of the last modified date of this group.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -61,4 +61,14 @@ public interface GroupManager
|
|||||||
* @return all groups assigned to the given member
|
* @return all groups assigned to the given member
|
||||||
*/
|
*/
|
||||||
public Collection<Group> getGroupsForMember(String member);
|
public Collection<Group> getGroupsForMember(String member);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link java.util.Collection} of filtered objects
|
||||||
|
*
|
||||||
|
* @param filter the searched string
|
||||||
|
* @return filtered object from the store
|
||||||
|
*/
|
||||||
|
Collection<Group> autocomplete(String filter);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,11 @@ public class GroupManagerDecorator
|
|||||||
return decorated.getGroupsForMember(member);
|
return decorated.getGroupsForMember(member);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Group> autocomplete(String filter) {
|
||||||
|
return decorated.autocomplete(filter);
|
||||||
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ import java.util.List;
|
|||||||
)
|
)
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
@XmlRootElement(name = "repositories")
|
@XmlRootElement(name = "repositories")
|
||||||
public class Repository extends BasicPropertiesAware implements ModelObject, PermissionObject {
|
public class Repository extends BasicPropertiesAware implements ModelObject, PermissionObject{
|
||||||
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 3486560714961909711L;
|
private static final long serialVersionUID = 3486560714961909711L;
|
||||||
|
|||||||
@@ -70,6 +70,12 @@ public class SearchRequest
|
|||||||
this.ignoreCase = ignoreCase;
|
this.ignoreCase = ignoreCase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SearchRequest(String query, boolean ignoreCase, int maxResults) {
|
||||||
|
this.query = query;
|
||||||
|
this.ignoreCase = ignoreCase;
|
||||||
|
this.maxResults = maxResults;
|
||||||
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ package sonia.scm.user;
|
|||||||
|
|
||||||
public class ChangePasswordNotAllowedException extends RuntimeException {
|
public class ChangePasswordNotAllowedException extends RuntimeException {
|
||||||
|
|
||||||
public static final String WRONG_USER_TYPE = "User of type {0} are not allowed to change password";
|
public static final String WRONG_USER_TYPE = "User of type %s are not allowed to change password";
|
||||||
|
|
||||||
public ChangePasswordNotAllowedException(String message) {
|
public ChangePasswordNotAllowedException(String type) {
|
||||||
super(message);
|
super(String.format(WRONG_USER_TYPE, type));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ package sonia.scm.user;
|
|||||||
|
|
||||||
public class InvalidPasswordException extends RuntimeException {
|
public class InvalidPasswordException extends RuntimeException {
|
||||||
|
|
||||||
public static final String INVALID_MATCHING = "The given Password does not match with the stored one.";
|
public InvalidPasswordException() {
|
||||||
|
super("The given Password does not match with the stored one.");
|
||||||
public InvalidPasswordException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import com.google.common.base.MoreObjects;
|
|||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
import sonia.scm.BasicPropertiesAware;
|
import sonia.scm.BasicPropertiesAware;
|
||||||
import sonia.scm.ModelObject;
|
import sonia.scm.ModelObject;
|
||||||
|
import sonia.scm.ReducedModelObject;
|
||||||
import sonia.scm.util.Util;
|
import sonia.scm.util.Util;
|
||||||
import sonia.scm.util.ValidationUtil;
|
import sonia.scm.util.ValidationUtil;
|
||||||
|
|
||||||
@@ -55,10 +56,13 @@ import java.security.Principal;
|
|||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@StaticPermissions(value = "user", globalPermissions = {"create", "list"})
|
@StaticPermissions(
|
||||||
|
value = "user",
|
||||||
|
globalPermissions = {"create", "list", "autocomplete"},
|
||||||
|
permissions = {"read", "modify", "delete", "changePassword"})
|
||||||
@XmlRootElement(name = "users")
|
@XmlRootElement(name = "users")
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public class User extends BasicPropertiesAware implements Principal, ModelObject, PermissionObject
|
public class User extends BasicPropertiesAware implements Principal, ModelObject, PermissionObject, ReducedModelObject
|
||||||
{
|
{
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
@@ -273,10 +277,6 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject
|
|||||||
//J+
|
//J+
|
||||||
}
|
}
|
||||||
|
|
||||||
public User changePassword(String password){
|
|
||||||
setPassword(password);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -38,10 +38,7 @@ package sonia.scm.user;
|
|||||||
import sonia.scm.Manager;
|
import sonia.scm.Manager;
|
||||||
import sonia.scm.search.Searchable;
|
import sonia.scm.search.Searchable;
|
||||||
|
|
||||||
import java.text.MessageFormat;
|
import java.util.Collection;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import static sonia.scm.user.ChangePasswordNotAllowedException.WRONG_USER_TYPE;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The central class for managing {@link User} objects.
|
* The central class for managing {@link User} objects.
|
||||||
@@ -74,21 +71,29 @@ public interface UserManager
|
|||||||
*/
|
*/
|
||||||
public String getDefaultType();
|
public String getDefaultType();
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Only account of the default type "xml" can change their password
|
|
||||||
*/
|
|
||||||
default Consumer<User> getUserTypeChecker() {
|
|
||||||
return user -> {
|
|
||||||
if (!isTypeDefault(user)) {
|
|
||||||
throw new ChangePasswordNotAllowedException(MessageFormat.format(WRONG_USER_TYPE, user.getType()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
default boolean isTypeDefault(User user) {
|
default boolean isTypeDefault(User user) {
|
||||||
return getDefaultType().equals(user.getType());
|
return getDefaultType().equals(user.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link java.util.Collection} of filtered objects
|
||||||
|
*
|
||||||
|
* @param filter the searched string
|
||||||
|
* @return filtered object from the store
|
||||||
|
*/
|
||||||
|
Collection<User> autocomplete(String filter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the password of the logged in user.
|
||||||
|
* @param oldPassword The current encrypted password of the user.
|
||||||
|
* @param newPassword The new encrypted password of the user.
|
||||||
|
*/
|
||||||
|
void changePasswordForLoggedInUser(String oldPassword, String newPassword);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overwrites the password for the given user id. This needs user write privileges.
|
||||||
|
* @param userId The id of the user to change the password for.
|
||||||
|
* @param newPassword The new encrypted password.
|
||||||
|
*/
|
||||||
|
void overwritePassword(String userId, String newPassword);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,7 +121,21 @@ public class UserManagerDecorator extends ManagerDecorator<User>
|
|||||||
return decorated.getDefaultType();
|
return decorated.getDefaultType();
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
@Override
|
||||||
|
public Collection<User> autocomplete(String filter) {
|
||||||
|
return decorated.autocomplete(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changePasswordForLoggedInUser(String oldPassword, String newPassword) {
|
||||||
|
decorated.changePasswordForLoggedInUser(oldPassword, newPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void overwritePassword(String userId, String newPassword) {
|
||||||
|
decorated.overwritePassword(userId, newPassword);
|
||||||
|
}
|
||||||
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private final UserManager decorated;
|
private final UserManager decorated;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ public class VndMediaType {
|
|||||||
public static final String INDEX = PREFIX + "index" + SUFFIX;
|
public static final String INDEX = PREFIX + "index" + SUFFIX;
|
||||||
public static final String USER = PREFIX + "user" + SUFFIX;
|
public static final String USER = PREFIX + "user" + SUFFIX;
|
||||||
public static final String GROUP = PREFIX + "group" + SUFFIX;
|
public static final String GROUP = PREFIX + "group" + SUFFIX;
|
||||||
|
public static final String AUTOCOMPLETE = PREFIX + "autocomplete" + SUFFIX;
|
||||||
public static final String REPOSITORY = PREFIX + "repository" + SUFFIX;
|
public static final String REPOSITORY = PREFIX + "repository" + SUFFIX;
|
||||||
public static final String PERMISSION = PREFIX + "permission" + SUFFIX;
|
public static final String PERMISSION = PREFIX + "permission" + SUFFIX;
|
||||||
public static final String CHANGESET = PREFIX + "changeset" + SUFFIX;
|
public static final String CHANGESET = PREFIX + "changeset" + SUFFIX;
|
||||||
@@ -38,6 +39,8 @@ public class VndMediaType {
|
|||||||
public static final String UI_PLUGIN_COLLECTION = PREFIX + "uiPluginCollection" + SUFFIX;
|
public static final String UI_PLUGIN_COLLECTION = PREFIX + "uiPluginCollection" + SUFFIX;
|
||||||
@SuppressWarnings("squid:S2068")
|
@SuppressWarnings("squid:S2068")
|
||||||
public static final String PASSWORD_CHANGE = PREFIX + "passwordChange" + SUFFIX;
|
public static final String PASSWORD_CHANGE = PREFIX + "passwordChange" + SUFFIX;
|
||||||
|
@SuppressWarnings("squid:S2068")
|
||||||
|
public static final String PASSWORD_OVERWRITE = PREFIX + "passwordOverwrite" + SUFFIX;
|
||||||
|
|
||||||
public static final String ME = PREFIX + "me" + SUFFIX;
|
public static final String ME = PREFIX + "me" + SUFFIX;
|
||||||
public static final String SOURCE = PREFIX + "source" + SUFFIX;
|
public static final String SOURCE = PREFIX + "source" + SUFFIX;
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ public class SyncingRealmHelperTest {
|
|||||||
* Tests {@link SyncingRealmHelper#store(Group)} with an existing group.
|
* Tests {@link SyncingRealmHelper#store(Group)} with an existing group.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testStoreGroupModify() throws NotFoundException {
|
public void testStoreGroupModify(){
|
||||||
Group group = new Group("unit-test", "heartOfGold");
|
Group group = new Group("unit-test", "heartOfGold");
|
||||||
|
|
||||||
when(groupManager.get("heartOfGold")).thenReturn(group);
|
when(groupManager.get("heartOfGold")).thenReturn(group);
|
||||||
@@ -191,7 +191,7 @@ public class SyncingRealmHelperTest {
|
|||||||
* Tests {@link SyncingRealmHelper#store(User)} with an existing user.
|
* Tests {@link SyncingRealmHelper#store(User)} with an existing user.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testStoreUserModify() throws NotFoundException {
|
public void testStoreUserModify(){
|
||||||
when(userManager.contains("tricia")).thenReturn(Boolean.TRUE);
|
when(userManager.contains("tricia")).thenReturn(Boolean.TRUE);
|
||||||
|
|
||||||
User user = new User("tricia");
|
User user = new User("tricia");
|
||||||
|
|||||||
@@ -35,17 +35,20 @@ package sonia.scm.xml;
|
|||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import sonia.scm.GenericDAO;
|
import sonia.scm.GenericDAO;
|
||||||
import sonia.scm.ModelObject;
|
import sonia.scm.ModelObject;
|
||||||
import sonia.scm.group.xml.XmlGroupDAO;
|
import sonia.scm.group.xml.XmlGroupDAO;
|
||||||
|
import sonia.scm.store.ConfigurationStore;
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
import sonia.scm.util.AssertUtil;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import sonia.scm.store.ConfigurationStore;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
73
scm-it/src/test/java/sonia/scm/it/AutoCompleteITCase.java
Normal file
73
scm-it/src/test/java/sonia/scm/it/AutoCompleteITCase.java
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package sonia.scm.it;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import sonia.scm.it.utils.ScmRequests;
|
||||||
|
import sonia.scm.it.utils.TestData;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
public class AutoCompleteITCase {
|
||||||
|
|
||||||
|
|
||||||
|
public static final String CREATED_USER_PREFIX = "user_";
|
||||||
|
public static final String CREATED_GROUP_PREFIX = "group_";
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
TestData.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void adminShouldAutoComplete() {
|
||||||
|
shouldAutocomplete(TestData.USER_SCM_ADMIN, TestData.USER_SCM_ADMIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void userShouldAutoComplete() {
|
||||||
|
String username = "nonAdmin";
|
||||||
|
String password = "pass";
|
||||||
|
TestData.createUser(username, password, false, "xml", "email@e.de");
|
||||||
|
shouldAutocomplete(username, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shouldAutocomplete(String username, String password) {
|
||||||
|
createUsers();
|
||||||
|
createGroups();
|
||||||
|
ScmRequests.start()
|
||||||
|
.requestIndexResource(username, password)
|
||||||
|
.assertStatusCode(200)
|
||||||
|
.requestAutoCompleteGroups("group*")
|
||||||
|
.assertStatusCode(200)
|
||||||
|
.assertAutoCompleteResults(assertAutoCompleteResult(CREATED_GROUP_PREFIX))
|
||||||
|
.returnToPrevious()
|
||||||
|
.requestAutoCompleteUsers("user*")
|
||||||
|
.assertStatusCode(200)
|
||||||
|
.assertAutoCompleteResults(assertAutoCompleteResult(CREATED_USER_PREFIX));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Consumer<List<Map>> assertAutoCompleteResult(String id) {
|
||||||
|
return autoCompleteDtos -> {
|
||||||
|
IntStream.range(0, 5).forEach(i -> {
|
||||||
|
assertThat(autoCompleteDtos).as("return maximum 5 entries").hasSize(5);
|
||||||
|
assertThat(autoCompleteDtos.get(i)).containsEntry("id", id + (i + 1));
|
||||||
|
assertThat(autoCompleteDtos.get(i)).containsEntry("displayName", id + (i + 1));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createUsers() {
|
||||||
|
IntStream.range(0, 6).forEach(i -> TestData.createUser(CREATED_USER_PREFIX + (i + 1), "pass", false, "xml", CREATED_USER_PREFIX + (i + 1) + "@scm-manager.org"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createGroups() {
|
||||||
|
IntStream.range(0, 6).forEach(i -> TestData.createGroup(CREATED_GROUP_PREFIX + (i + 1), CREATED_GROUP_PREFIX + (i + 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -20,12 +20,9 @@ public class MeITCase {
|
|||||||
String newPassword = TestData.USER_SCM_ADMIN + "1";
|
String newPassword = TestData.USER_SCM_ADMIN + "1";
|
||||||
// admin change the own password
|
// admin change the own password
|
||||||
ScmRequests.start()
|
ScmRequests.start()
|
||||||
.given()
|
.requestIndexResource(TestData.USER_SCM_ADMIN, TestData.USER_SCM_ADMIN)
|
||||||
.url(TestData.getMeUrl())
|
.requestMe()
|
||||||
.usernameAndPassword(TestData.USER_SCM_ADMIN, TestData.USER_SCM_ADMIN)
|
|
||||||
.getMeResource()
|
|
||||||
.assertStatusCode(200)
|
.assertStatusCode(200)
|
||||||
.usingMeResponse()
|
|
||||||
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE))
|
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE))
|
||||||
.assertPassword(Assert::assertNull)
|
.assertPassword(Assert::assertNull)
|
||||||
.assertType(s -> assertThat(s).isEqualTo("xml"))
|
.assertType(s -> assertThat(s).isEqualTo("xml"))
|
||||||
@@ -33,30 +30,48 @@ public class MeITCase {
|
|||||||
.assertStatusCode(204);
|
.assertStatusCode(204);
|
||||||
// assert password is changed -> login with the new Password than undo changes
|
// assert password is changed -> login with the new Password than undo changes
|
||||||
ScmRequests.start()
|
ScmRequests.start()
|
||||||
.given()
|
.requestIndexResource(TestData.USER_SCM_ADMIN, newPassword)
|
||||||
.url(TestData.getUserUrl(TestData.USER_SCM_ADMIN))
|
.requestMe()
|
||||||
.usernameAndPassword(TestData.USER_SCM_ADMIN, newPassword)
|
|
||||||
.getMeResource()
|
|
||||||
.assertStatusCode(200)
|
.assertStatusCode(200)
|
||||||
.usingMeResponse()
|
|
||||||
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE))// still admin
|
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE))// still admin
|
||||||
.requestChangePassword(newPassword, TestData.USER_SCM_ADMIN)
|
.requestChangePassword(newPassword, TestData.USER_SCM_ADMIN)
|
||||||
.assertStatusCode(204);
|
.assertStatusCode(204);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nonAdminUserShouldChangeOwnPassword() {
|
||||||
|
String newPassword = "pass1";
|
||||||
|
String username = "user1";
|
||||||
|
String password = "pass";
|
||||||
|
TestData.createUser(username, password,false,"xml", "em@l.de");
|
||||||
|
// user change the own password
|
||||||
|
ScmRequests.start()
|
||||||
|
.requestIndexResource(username, password)
|
||||||
|
.requestMe()
|
||||||
|
.assertStatusCode(200)
|
||||||
|
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.FALSE))
|
||||||
|
.assertPassword(Assert::assertNull)
|
||||||
|
.assertType(s -> assertThat(s).isEqualTo("xml"))
|
||||||
|
.requestChangePassword(password, newPassword)
|
||||||
|
.assertStatusCode(204);
|
||||||
|
// assert password is changed -> login with the new Password than undo changes
|
||||||
|
ScmRequests.start()
|
||||||
|
.requestIndexResource(username, newPassword)
|
||||||
|
.requestMe()
|
||||||
|
.assertStatusCode(200);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldHidePasswordLinkIfUserTypeIsNotXML() {
|
public void shouldHidePasswordLinkIfUserTypeIsNotXML() {
|
||||||
String newUser = "user";
|
String newUser = "user";
|
||||||
String password = "pass";
|
String password = "pass";
|
||||||
String type = "not XML Type";
|
String type = "not XML Type";
|
||||||
TestData.createUser(newUser, password, true, type);
|
TestData.createUser(newUser, password, true, type, "user@scm-manager.org");
|
||||||
ScmRequests.start()
|
ScmRequests.start()
|
||||||
.given()
|
.requestIndexResource(newUser, password)
|
||||||
.url(TestData.getMeUrl())
|
.requestMe()
|
||||||
.usernameAndPassword(newUser, password)
|
|
||||||
.getMeResource()
|
|
||||||
.assertStatusCode(200)
|
.assertStatusCode(200)
|
||||||
.usingMeResponse()
|
|
||||||
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE))
|
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE))
|
||||||
.assertPassword(Assert::assertNull)
|
.assertPassword(Assert::assertNull)
|
||||||
.assertType(s -> assertThat(s).isEqualTo(type))
|
.assertType(s -> assertThat(s).isEqualTo(type))
|
||||||
|
|||||||
@@ -87,13 +87,13 @@ public class PermissionsITCase {
|
|||||||
@Before
|
@Before
|
||||||
public void prepareEnvironment() {
|
public void prepareEnvironment() {
|
||||||
TestData.createDefault();
|
TestData.createDefault();
|
||||||
TestData.createUser(USER_READ, USER_PASS);
|
TestData.createNotAdminUser(USER_READ, USER_PASS);
|
||||||
TestData.createUserPermission(USER_READ, PermissionType.READ, repositoryType);
|
TestData.createUserPermission(USER_READ, PermissionType.READ, repositoryType);
|
||||||
TestData.createUser(USER_WRITE, USER_PASS);
|
TestData.createNotAdminUser(USER_WRITE, USER_PASS);
|
||||||
TestData.createUserPermission(USER_WRITE, PermissionType.WRITE, repositoryType);
|
TestData.createUserPermission(USER_WRITE, PermissionType.WRITE, repositoryType);
|
||||||
TestData.createUser(USER_OWNER, USER_PASS);
|
TestData.createNotAdminUser(USER_OWNER, USER_PASS);
|
||||||
TestData.createUserPermission(USER_OWNER, PermissionType.OWNER, repositoryType);
|
TestData.createUserPermission(USER_OWNER, PermissionType.OWNER, repositoryType);
|
||||||
TestData.createUser(USER_OTHER, USER_PASS);
|
TestData.createNotAdminUser(USER_OTHER, USER_PASS);
|
||||||
createdPermissions = 3;
|
createdPermissions = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public class RepositoryAccessITCase {
|
|||||||
|
|
||||||
private final String repositoryType;
|
private final String repositoryType;
|
||||||
private File folder;
|
private File folder;
|
||||||
private ScmRequests.AppliedRepositoryRequest repositoryGetRequest;
|
private ScmRequests.RepositoryResponse<ScmRequests.IndexResponse> repositoryResponse;
|
||||||
|
|
||||||
public RepositoryAccessITCase(String repositoryType) {
|
public RepositoryAccessITCase(String repositoryType) {
|
||||||
this.repositoryType = repositoryType;
|
this.repositoryType = repositoryType;
|
||||||
@@ -59,17 +59,13 @@ public class RepositoryAccessITCase {
|
|||||||
public void init() {
|
public void init() {
|
||||||
TestData.createDefault();
|
TestData.createDefault();
|
||||||
folder = tempFolder.getRoot();
|
folder = tempFolder.getRoot();
|
||||||
repositoryGetRequest = ScmRequests.start()
|
String namespace = ADMIN_USERNAME;
|
||||||
.given()
|
String repo = TestData.getDefaultRepoName(repositoryType);
|
||||||
.url(TestData.getDefaultRepositoryUrl(repositoryType))
|
repositoryResponse =
|
||||||
.usernameAndPassword(ADMIN_USERNAME, ADMIN_PASSWORD)
|
ScmRequests.start()
|
||||||
.getRepositoryResource()
|
.requestIndexResource(ADMIN_USERNAME, ADMIN_PASSWORD)
|
||||||
|
.requestRepository(namespace, repo)
|
||||||
.assertStatusCode(HttpStatus.SC_OK);
|
.assertStatusCode(HttpStatus.SC_OK);
|
||||||
ScmRequests.AppliedMeRequest meGetRequest = ScmRequests.start()
|
|
||||||
.given()
|
|
||||||
.url(TestData.getMeUrl())
|
|
||||||
.usernameAndPassword(ADMIN_USERNAME, ADMIN_PASSWORD)
|
|
||||||
.getMeResource();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -306,17 +302,12 @@ public class RepositoryAccessITCase {
|
|||||||
public void shouldFindFileHistory() throws IOException {
|
public void shouldFindFileHistory() throws IOException {
|
||||||
RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder);
|
RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder);
|
||||||
Changeset changeset = RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "folder/subfolder/a.txt", "a");
|
Changeset changeset = RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, "folder/subfolder/a.txt", "a");
|
||||||
repositoryGetRequest
|
repositoryResponse
|
||||||
.usingRepositoryResponse()
|
|
||||||
.requestSources()
|
.requestSources()
|
||||||
.usingSourcesResponse()
|
|
||||||
.requestSelf("folder")
|
.requestSelf("folder")
|
||||||
.usingSourcesResponse()
|
|
||||||
.requestSelf("subfolder")
|
.requestSelf("subfolder")
|
||||||
.usingSourcesResponse()
|
|
||||||
.requestFileHistory("a.txt")
|
.requestFileHistory("a.txt")
|
||||||
.assertStatusCode(HttpStatus.SC_OK)
|
.assertStatusCode(HttpStatus.SC_OK)
|
||||||
.usingChangesetsResponse()
|
|
||||||
.assertChangesets(changesets -> {
|
.assertChangesets(changesets -> {
|
||||||
assertThat(changesets).hasSize(1);
|
assertThat(changesets).hasSize(1);
|
||||||
assertThat(changesets.get(0)).containsEntry("id", changeset.getId());
|
assertThat(changesets.get(0)).containsEntry("id", changeset.getId());
|
||||||
@@ -332,14 +323,11 @@ public class RepositoryAccessITCase {
|
|||||||
String fileName = "a.txt";
|
String fileName = "a.txt";
|
||||||
Changeset changeset = RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName, "a");
|
Changeset changeset = RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName, "a");
|
||||||
String revision = changeset.getId();
|
String revision = changeset.getId();
|
||||||
repositoryGetRequest
|
repositoryResponse
|
||||||
.usingRepositoryResponse()
|
|
||||||
.requestChangesets()
|
.requestChangesets()
|
||||||
.assertStatusCode(HttpStatus.SC_OK)
|
.assertStatusCode(HttpStatus.SC_OK)
|
||||||
.usingChangesetsResponse()
|
|
||||||
.requestModifications(revision)
|
.requestModifications(revision)
|
||||||
.assertStatusCode(HttpStatus.SC_OK)
|
.assertStatusCode(HttpStatus.SC_OK)
|
||||||
.usingModificationsResponse()
|
|
||||||
.assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision))
|
.assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision))
|
||||||
.assertAdded(addedFiles -> assertThat(addedFiles)
|
.assertAdded(addedFiles -> assertThat(addedFiles)
|
||||||
.hasSize(1)
|
.hasSize(1)
|
||||||
@@ -359,14 +347,11 @@ public class RepositoryAccessITCase {
|
|||||||
Changeset changeset = RepositoryUtil.removeAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName);
|
Changeset changeset = RepositoryUtil.removeAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName);
|
||||||
|
|
||||||
String revision = changeset.getId();
|
String revision = changeset.getId();
|
||||||
repositoryGetRequest
|
repositoryResponse
|
||||||
.usingRepositoryResponse()
|
|
||||||
.requestChangesets()
|
.requestChangesets()
|
||||||
.assertStatusCode(HttpStatus.SC_OK)
|
.assertStatusCode(HttpStatus.SC_OK)
|
||||||
.usingChangesetsResponse()
|
|
||||||
.requestModifications(revision)
|
.requestModifications(revision)
|
||||||
.assertStatusCode(HttpStatus.SC_OK)
|
.assertStatusCode(HttpStatus.SC_OK)
|
||||||
.usingModificationsResponse()
|
|
||||||
.assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision))
|
.assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision))
|
||||||
.assertRemoved(removedFiles -> assertThat(removedFiles)
|
.assertRemoved(removedFiles -> assertThat(removedFiles)
|
||||||
.hasSize(1)
|
.hasSize(1)
|
||||||
@@ -386,14 +371,11 @@ public class RepositoryAccessITCase {
|
|||||||
Changeset changeset = RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName, "new Content");
|
Changeset changeset = RepositoryUtil.createAndCommitFile(repositoryClient, ADMIN_USERNAME, fileName, "new Content");
|
||||||
|
|
||||||
String revision = changeset.getId();
|
String revision = changeset.getId();
|
||||||
repositoryGetRequest
|
repositoryResponse
|
||||||
.usingRepositoryResponse()
|
|
||||||
.requestChangesets()
|
.requestChangesets()
|
||||||
.assertStatusCode(HttpStatus.SC_OK)
|
.assertStatusCode(HttpStatus.SC_OK)
|
||||||
.usingChangesetsResponse()
|
|
||||||
.requestModifications(revision)
|
.requestModifications(revision)
|
||||||
.assertStatusCode(HttpStatus.SC_OK)
|
.assertStatusCode(HttpStatus.SC_OK)
|
||||||
.usingModificationsResponse()
|
|
||||||
.assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision))
|
.assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision))
|
||||||
.assertModified(modifiedFiles -> assertThat(modifiedFiles)
|
.assertModified(modifiedFiles -> assertThat(modifiedFiles)
|
||||||
.hasSize(1)
|
.hasSize(1)
|
||||||
@@ -423,14 +405,11 @@ public class RepositoryAccessITCase {
|
|||||||
Changeset changeset = RepositoryUtil.commitMultipleFileModifications(repositoryClient, ADMIN_USERNAME, addedFiles, modifiedFiles, removedFiles);
|
Changeset changeset = RepositoryUtil.commitMultipleFileModifications(repositoryClient, ADMIN_USERNAME, addedFiles, modifiedFiles, removedFiles);
|
||||||
|
|
||||||
String revision = changeset.getId();
|
String revision = changeset.getId();
|
||||||
repositoryGetRequest
|
repositoryResponse
|
||||||
.usingRepositoryResponse()
|
|
||||||
.requestChangesets()
|
.requestChangesets()
|
||||||
.assertStatusCode(HttpStatus.SC_OK)
|
.assertStatusCode(HttpStatus.SC_OK)
|
||||||
.usingChangesetsResponse()
|
|
||||||
.requestModifications(revision)
|
.requestModifications(revision)
|
||||||
.assertStatusCode(HttpStatus.SC_OK)
|
.assertStatusCode(HttpStatus.SC_OK)
|
||||||
.usingModificationsResponse()
|
|
||||||
.assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision))
|
.assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision))
|
||||||
.assertAdded(a -> assertThat(a)
|
.assertAdded(a -> assertThat(a)
|
||||||
.hasSize(1)
|
.hasSize(1)
|
||||||
@@ -463,14 +442,11 @@ public class RepositoryAccessITCase {
|
|||||||
Changeset changeset = RepositoryUtil.commitMultipleFileModifications(repositoryClient, ADMIN_USERNAME, addedFiles, modifiedFiles, removedFiles);
|
Changeset changeset = RepositoryUtil.commitMultipleFileModifications(repositoryClient, ADMIN_USERNAME, addedFiles, modifiedFiles, removedFiles);
|
||||||
|
|
||||||
String revision = changeset.getId();
|
String revision = changeset.getId();
|
||||||
repositoryGetRequest
|
repositoryResponse
|
||||||
.usingRepositoryResponse()
|
|
||||||
.requestChangesets()
|
.requestChangesets()
|
||||||
.assertStatusCode(HttpStatus.SC_OK)
|
.assertStatusCode(HttpStatus.SC_OK)
|
||||||
.usingChangesetsResponse()
|
|
||||||
.requestModifications(revision)
|
.requestModifications(revision)
|
||||||
.assertStatusCode(HttpStatus.SC_OK)
|
.assertStatusCode(HttpStatus.SC_OK)
|
||||||
.usingModificationsResponse()
|
|
||||||
.assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision))
|
.assertRevision(actualRevision -> assertThat(actualRevision).isEqualTo(revision))
|
||||||
.assertAdded(a -> assertThat(a)
|
.assertAdded(a -> assertThat(a)
|
||||||
.hasSize(3)
|
.hasSize(3)
|
||||||
|
|||||||
@@ -19,75 +19,83 @@ public class UserITCase {
|
|||||||
public void adminShouldChangeOwnPassword() {
|
public void adminShouldChangeOwnPassword() {
|
||||||
String newUser = "user";
|
String newUser = "user";
|
||||||
String password = "pass";
|
String password = "pass";
|
||||||
TestData.createUser(newUser, password, true, "xml");
|
TestData.createUser(newUser, password, true, "xml", "user@scm-manager.org");
|
||||||
String newPassword = "new_password";
|
String newPassword = "new_password";
|
||||||
// admin change the own password
|
// admin change the own password
|
||||||
ScmRequests.start()
|
ScmRequests.start()
|
||||||
.given()
|
.requestIndexResource(newUser, password)
|
||||||
.url(TestData.getUserUrl(newUser))
|
.assertStatusCode(200)
|
||||||
.usernameAndPassword(newUser, password)
|
.requestUser(newUser)
|
||||||
.getUserResource()
|
|
||||||
.assertStatusCode(200)
|
.assertStatusCode(200)
|
||||||
.usingUserResponse()
|
|
||||||
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE))
|
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE))
|
||||||
.assertPassword(Assert::assertNull)
|
.assertPassword(Assert::assertNull)
|
||||||
.requestChangePassword(newPassword) // the oldPassword is not needed in the user resource
|
.requestChangePassword(newPassword)
|
||||||
.assertStatusCode(204);
|
.assertStatusCode(204);
|
||||||
// assert password is changed -> login with the new Password
|
// assert password is changed -> login with the new Password
|
||||||
ScmRequests.start()
|
ScmRequests.start()
|
||||||
.given()
|
.requestIndexResource(newUser, newPassword)
|
||||||
.url(TestData.getUserUrl(newUser))
|
|
||||||
.usernameAndPassword(newUser, newPassword)
|
|
||||||
.getUserResource()
|
|
||||||
.assertStatusCode(200)
|
.assertStatusCode(200)
|
||||||
.usingUserResponse()
|
.requestUser(newUser)
|
||||||
.assertAdmin(isAdmin -> assertThat(isAdmin).isEqualTo(Boolean.TRUE))
|
.assertAdmin(isAdmin -> assertThat(isAdmin).isEqualTo(Boolean.TRUE))
|
||||||
.assertPassword(Assert::assertNull);
|
.assertPassword(Assert::assertNull);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void adminShouldChangePasswordOfOtherUser() {
|
public void adminShouldChangePasswordOfOtherUser() {
|
||||||
String newUser = "user";
|
String newUser = "user";
|
||||||
String password = "pass";
|
String password = "pass";
|
||||||
TestData.createUser(newUser, password, true, "xml");
|
TestData.createUser(newUser, password, true, "xml", "user@scm-manager.org");
|
||||||
String newPassword = "new_password";
|
String newPassword = "new_password";
|
||||||
// admin change the password of the user
|
// admin change the password of the user
|
||||||
ScmRequests.start()
|
ScmRequests.start()
|
||||||
.given()
|
.requestIndexResource(TestData.USER_SCM_ADMIN, TestData.USER_SCM_ADMIN)
|
||||||
.url(TestData.getUserUrl(newUser))// the admin get the user object
|
.assertStatusCode(200)
|
||||||
.usernameAndPassword(TestData.USER_SCM_ADMIN, TestData.USER_SCM_ADMIN)
|
.requestUser(newUser)
|
||||||
.getUserResource()
|
|
||||||
.assertStatusCode(200)
|
.assertStatusCode(200)
|
||||||
.usingUserResponse()
|
|
||||||
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE)) // the user anonymous is not an admin
|
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE)) // the user anonymous is not an admin
|
||||||
.assertPassword(Assert::assertNull)
|
.assertPassword(Assert::assertNull)
|
||||||
.requestChangePassword(newPassword) // the oldPassword is not needed in the user resource
|
.requestChangePassword(newPassword) // the oldPassword is not needed in the user resource
|
||||||
.assertStatusCode(204);
|
.assertStatusCode(204);
|
||||||
// assert password is changed
|
// assert password is changed
|
||||||
ScmRequests.start()
|
ScmRequests.start()
|
||||||
.given()
|
.requestIndexResource(newUser, newPassword)
|
||||||
.url(TestData.getUserUrl(newUser))
|
.assertStatusCode(200)
|
||||||
.usernameAndPassword(newUser, newPassword)
|
.requestUser(newUser)
|
||||||
.getUserResource()
|
|
||||||
.assertStatusCode(200);
|
.assertStatusCode(200);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nonAdminUserShouldNotChangePasswordOfOtherUser() {
|
||||||
|
String user = "user";
|
||||||
|
String password = "pass";
|
||||||
|
TestData.createUser(user, password, false, "xml", "em@l.de");
|
||||||
|
String user2 = "user2";
|
||||||
|
TestData.createUser(user2, password, false, "xml", "em@l.de");
|
||||||
|
ScmRequests.start()
|
||||||
|
.requestIndexResource(user, password)
|
||||||
|
.assertUsersLinkDoesNotExists();
|
||||||
|
// use the users/ endpoint bypassed the index resource
|
||||||
|
ScmRequests.start()
|
||||||
|
.requestUser(user, password, user2)
|
||||||
|
.assertStatusCode(403);
|
||||||
|
// use the users/password endpoint bypassed the index and users resources
|
||||||
|
ScmRequests.start()
|
||||||
|
.requestUserChangePassword(user, password, user2, "newPassword")
|
||||||
|
.assertStatusCode(403);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldHidePasswordLinkIfUserTypeIsNotXML() {
|
public void shouldHidePasswordLinkIfUserTypeIsNotXML() {
|
||||||
String newUser = "user";
|
String newUser = "user";
|
||||||
String password = "pass";
|
String password = "pass";
|
||||||
String type = "not XML Type";
|
String type = "not XML Type";
|
||||||
TestData.createUser(newUser, password, true, type);
|
TestData.createUser(newUser, password, true, type, "user@scm-manager.org");
|
||||||
ScmRequests.start()
|
ScmRequests.start()
|
||||||
.given()
|
.requestIndexResource(newUser, password)
|
||||||
.url(TestData.getMeUrl())
|
.assertStatusCode(200)
|
||||||
.usernameAndPassword(newUser, password)
|
.requestUser(newUser)
|
||||||
.getUserResource()
|
|
||||||
.assertStatusCode(200)
|
.assertStatusCode(200)
|
||||||
.usingUserResponse()
|
|
||||||
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE))
|
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE))
|
||||||
.assertPassword(Assert::assertNull)
|
.assertPassword(Assert::assertNull)
|
||||||
.assertType(s -> assertThat(s).isEqualTo(type))
|
.assertType(s -> assertThat(s).isEqualTo(type))
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ package sonia.scm.it.utils;
|
|||||||
|
|
||||||
import io.restassured.RestAssured;
|
import io.restassured.RestAssured;
|
||||||
import io.restassured.response.Response;
|
import io.restassured.response.Response;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@@ -25,7 +27,8 @@ import static sonia.scm.it.utils.TestData.createPasswordChangeJson;
|
|||||||
*/
|
*/
|
||||||
public class ScmRequests {
|
public class ScmRequests {
|
||||||
|
|
||||||
private String url;
|
private static final Logger LOG = LoggerFactory.getLogger(ScmRequests.class);
|
||||||
|
|
||||||
private String username;
|
private String username;
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
@@ -33,10 +36,24 @@ public class ScmRequests {
|
|||||||
return new ScmRequests();
|
return new ScmRequests();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Given given() {
|
public IndexResponse requestIndexResource(String username, String password) {
|
||||||
return new Given();
|
setUsername(username);
|
||||||
|
setPassword(password);
|
||||||
|
return new IndexResponse(applyGETRequest(RestUtil.REST_BASE_URL.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public <SELF extends UserResponse<SELF, T>, T extends ModelResponse> UserResponse<SELF,T> requestUser(String username, String password, String pathParam) {
|
||||||
|
setUsername(username);
|
||||||
|
setPassword(password);
|
||||||
|
return new UserResponse<>(applyGETRequest(RestUtil.REST_BASE_URL.resolve("users/"+pathParam).toString()), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChangePasswordResponse<ChangePasswordResponse> requestUserChangePassword(String username, String password, String userPathParam, String newPassword) {
|
||||||
|
setUsername(username);
|
||||||
|
setPassword(password);
|
||||||
|
return new ChangePasswordResponse<>(applyPUTRequest(RestUtil.REST_BASE_URL.resolve("users/"+userPathParam+"/password").toString(), VndMediaType.PASSWORD_OVERWRITE, TestData.createPasswordChangeJson(password,newPassword)), null);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply a GET Request to the extracted url from the given link
|
* Apply a GET Request to the extracted url from the given link
|
||||||
@@ -46,24 +63,47 @@ public class ScmRequests {
|
|||||||
* @return the response of the GET request using the given link
|
* @return the response of the GET request using the given link
|
||||||
*/
|
*/
|
||||||
private Response applyGETRequestFromLink(Response response, String linkPropertyName) {
|
private Response applyGETRequestFromLink(Response response, String linkPropertyName) {
|
||||||
return applyGETRequest(response
|
return applyGETRequestFromLinkWithParams(response, linkPropertyName, "");
|
||||||
.then()
|
|
||||||
.extract()
|
|
||||||
.path(linkPropertyName));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a GET Request to the extracted url from the given link
|
||||||
|
*
|
||||||
|
* @param linkPropertyName the property name of link
|
||||||
|
* @param response the response containing the link
|
||||||
|
* @param params query params eg. ?q=xyz&count=12 or path params eg. namespace/name
|
||||||
|
* @return the response of the GET request using the given link
|
||||||
|
*/
|
||||||
|
private Response applyGETRequestFromLinkWithParams(Response response, String linkPropertyName, String params) {
|
||||||
|
return applyGETRequestWithQueryParams(response
|
||||||
|
.then()
|
||||||
|
.extract()
|
||||||
|
.path(linkPropertyName), params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a GET Request to the given <code>url</code> and return the response.
|
||||||
|
*
|
||||||
|
* @param url the url of the GET request
|
||||||
|
* @param params query params eg. ?q=xyz&count=12 or path params eg. namespace/name
|
||||||
|
* @return the response of the GET request using the given <code>url</code>
|
||||||
|
*/
|
||||||
|
private Response applyGETRequestWithQueryParams(String url, String params) {
|
||||||
|
LOG.info("GET {}", url);
|
||||||
|
return RestAssured.given()
|
||||||
|
.auth().preemptive().basic(username, password)
|
||||||
|
.when()
|
||||||
|
.get(url + params);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply a GET Request to the given <code>url</code> and return the response.
|
* Apply a GET Request to the given <code>url</code> and return the response.
|
||||||
*
|
*
|
||||||
* @param url the url of the GET request
|
* @param url the url of the GET request
|
||||||
* @return the response of the GET request using the given <code>url</code>
|
* @return the response of the GET request using the given <code>url</code>
|
||||||
*/
|
**/
|
||||||
private Response applyGETRequest(String url) {
|
private Response applyGETRequest(String url) {
|
||||||
return RestAssured.given()
|
return applyGETRequestWithQueryParams(url, "");
|
||||||
.auth().preemptive().basic(username, password)
|
|
||||||
.when()
|
|
||||||
.get(url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -92,6 +132,7 @@ public class ScmRequests {
|
|||||||
* @return the response of the PUT request using the given <code>url</code>
|
* @return the response of the PUT request using the given <code>url</code>
|
||||||
*/
|
*/
|
||||||
private Response applyPUTRequest(String url, String mediaType, String body) {
|
private Response applyPUTRequest(String url, String mediaType, String body) {
|
||||||
|
LOG.info("PUT {}", url);
|
||||||
return RestAssured.given()
|
return RestAssured.given()
|
||||||
.auth().preemptive().basic(username, password)
|
.auth().preemptive().basic(username, password)
|
||||||
.when()
|
.when()
|
||||||
@@ -101,11 +142,6 @@ public class ScmRequests {
|
|||||||
.put(url);
|
.put(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void setUrl(String url) {
|
|
||||||
this.url = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setUsername(String username) {
|
private void setUsername(String username) {
|
||||||
this.username = username;
|
this.username = username;
|
||||||
}
|
}
|
||||||
@@ -114,272 +150,163 @@ public class ScmRequests {
|
|||||||
this.password = password;
|
this.password = password;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getUrl() {
|
public class IndexResponse extends ModelResponse<IndexResponse, IndexResponse> {
|
||||||
return url;
|
public static final String LINK_AUTOCOMPLETE_USERS = "_links.autocomplete.find{it.name=='users'}.href";
|
||||||
}
|
public static final String LINK_AUTOCOMPLETE_GROUPS = "_links.autocomplete.find{it.name=='groups'}.href";
|
||||||
|
public static final String LINK_REPOSITORIES = "_links.repositories.href";
|
||||||
|
private static final String LINK_ME = "_links.me.href";
|
||||||
|
private static final String LINK_USERS = "_links.users.href";
|
||||||
|
|
||||||
private String getUsername() {
|
public IndexResponse(Response response) {
|
||||||
return username;
|
super(response, null);
|
||||||
}
|
|
||||||
|
|
||||||
private String getPassword() {
|
|
||||||
return password;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Given {
|
|
||||||
|
|
||||||
public GivenUrl url(String url) {
|
|
||||||
setUrl(url);
|
|
||||||
return new GivenUrl();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public GivenUrl url(URI url) {
|
public AutoCompleteResponse<IndexResponse> requestAutoCompleteUsers(String q) {
|
||||||
setUrl(url.toString());
|
return new AutoCompleteResponse<>(applyGETRequestFromLinkWithParams(response, LINK_AUTOCOMPLETE_USERS, "?q=" + q), this);
|
||||||
return new GivenUrl();
|
}
|
||||||
|
|
||||||
|
public AutoCompleteResponse<IndexResponse> requestAutoCompleteGroups(String q) {
|
||||||
|
return new AutoCompleteResponse<>(applyGETRequestFromLinkWithParams(response, LINK_AUTOCOMPLETE_GROUPS, "?q=" + q), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RepositoryResponse<IndexResponse> requestRepository(String namespace, String name) {
|
||||||
|
return new RepositoryResponse<>(applyGETRequestFromLinkWithParams(response, LINK_REPOSITORIES, namespace + "/" + name), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MeResponse<IndexResponse> requestMe() {
|
||||||
|
return new MeResponse<>(applyGETRequestFromLink(response, LINK_ME), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserResponse<? extends UserResponse, IndexResponse> requestUser(String username) {
|
||||||
|
return new UserResponse<>(applyGETRequestFromLinkWithParams(response, LINK_USERS, username), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IndexResponse assertUsersLinkDoesNotExists() {
|
||||||
|
return super.assertPropertyPathDoesNotExists(LINK_USERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RepositoryResponse<PREV extends ModelResponse> extends ModelResponse<RepositoryResponse<PREV>, PREV> {
|
||||||
|
|
||||||
|
|
||||||
|
public static final String LINKS_SOURCES = "_links.sources.href";
|
||||||
|
public static final String LINKS_CHANGESETS = "_links.changesets.href";
|
||||||
|
|
||||||
|
public RepositoryResponse(Response response, PREV previousResponse) {
|
||||||
|
super(response, previousResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SourcesResponse<RepositoryResponse> requestSources() {
|
||||||
|
return new SourcesResponse<>(applyGETRequestFromLink(response, LINKS_SOURCES), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChangesetsResponse<RepositoryResponse> requestChangesets() {
|
||||||
|
return new ChangesetsResponse<>(applyGETRequestFromLink(response, LINKS_CHANGESETS), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GivenWithUrlAndAuth {
|
public class ChangesetsResponse<PREV extends ModelResponse> extends ModelResponse<ChangesetsResponse<PREV>, PREV> {
|
||||||
public AppliedMeRequest getMeResource() {
|
|
||||||
return new AppliedMeRequest(applyGETRequest(url));
|
|
||||||
}
|
|
||||||
|
|
||||||
public AppliedUserRequest getUserResource() {
|
public ChangesetsResponse(Response response, PREV previousResponse) {
|
||||||
return new AppliedUserRequest(applyGETRequest(url));
|
super(response, previousResponse);
|
||||||
}
|
|
||||||
|
|
||||||
public AppliedRepositoryRequest getRepositoryResource() {
|
|
||||||
return new AppliedRepositoryRequest(
|
|
||||||
applyGETRequest(url)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AppliedRequest<SELF extends AppliedRequest> {
|
|
||||||
private Response response;
|
|
||||||
|
|
||||||
public AppliedRequest(Response response) {
|
|
||||||
this.response = response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* apply custom assertions to the actual response
|
|
||||||
*
|
|
||||||
* @param consumer consume the response in order to assert the content. the header, the payload etc..
|
|
||||||
* @return the self object
|
|
||||||
*/
|
|
||||||
public SELF assertResponse(Consumer<Response> consumer) {
|
|
||||||
consumer.accept(response);
|
|
||||||
return (SELF) this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* special assertion of the status code
|
|
||||||
*
|
|
||||||
* @param expectedStatusCode the expected status code
|
|
||||||
* @return the self object
|
|
||||||
*/
|
|
||||||
public SELF assertStatusCode(int expectedStatusCode) {
|
|
||||||
this.response.then().assertThat().statusCode(expectedStatusCode);
|
|
||||||
return (SELF) this;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AppliedRepositoryRequest extends AppliedRequest<AppliedRepositoryRequest> {
|
|
||||||
|
|
||||||
public AppliedRepositoryRequest(Response response) {
|
|
||||||
super(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RepositoryResponse usingRepositoryResponse() {
|
|
||||||
return new RepositoryResponse(super.response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RepositoryResponse {
|
|
||||||
|
|
||||||
private Response repositoryResponse;
|
|
||||||
|
|
||||||
public RepositoryResponse(Response repositoryResponse) {
|
|
||||||
this.repositoryResponse = repositoryResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AppliedSourcesRequest requestSources() {
|
|
||||||
return new AppliedSourcesRequest(applyGETRequestFromLink(repositoryResponse, "_links.sources.href"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public AppliedChangesetsRequest requestChangesets() {
|
|
||||||
return new AppliedChangesetsRequest(applyGETRequestFromLink(repositoryResponse, "_links.changesets.href"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AppliedChangesetsRequest extends AppliedRequest<AppliedChangesetsRequest> {
|
|
||||||
|
|
||||||
public AppliedChangesetsRequest(Response response) {
|
|
||||||
super(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChangesetsResponse usingChangesetsResponse() {
|
|
||||||
return new ChangesetsResponse(super.response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ChangesetsResponse {
|
|
||||||
private Response changesetsResponse;
|
|
||||||
|
|
||||||
public ChangesetsResponse(Response changesetsResponse) {
|
|
||||||
this.changesetsResponse = changesetsResponse;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChangesetsResponse assertChangesets(Consumer<List<Map>> changesetsConsumer) {
|
public ChangesetsResponse assertChangesets(Consumer<List<Map>> changesetsConsumer) {
|
||||||
List<Map> changesets = changesetsResponse.then().extract().path("_embedded.changesets");
|
List<Map> changesets = response.then().extract().path("_embedded.changesets");
|
||||||
changesetsConsumer.accept(changesets);
|
changesetsConsumer.accept(changesets);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AppliedDiffRequest requestDiff(String revision) {
|
public DiffResponse<ChangesetsResponse> requestDiff(String revision) {
|
||||||
return new AppliedDiffRequest(applyGETRequestFromLink(changesetsResponse, "_embedded.changesets.find{it.id=='" + revision + "'}._links.diff.href"));
|
return new DiffResponse<>(applyGETRequestFromLink(response, "_embedded.changesets.find{it.id=='" + revision + "'}._links.diff.href"), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AppliedModificationsRequest requestModifications(String revision) {
|
public ModificationsResponse<ChangesetsResponse> requestModifications(String revision) {
|
||||||
return new AppliedModificationsRequest(applyGETRequestFromLink(changesetsResponse, "_embedded.changesets.find{it.id=='" + revision + "'}._links.modifications.href"));
|
return new ModificationsResponse<>(applyGETRequestFromLink(response, "_embedded.changesets.find{it.id=='" + revision + "'}._links.modifications.href"), this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AppliedSourcesRequest extends AppliedRequest<AppliedSourcesRequest> {
|
|
||||||
|
|
||||||
public AppliedSourcesRequest(Response sourcesResponse) {
|
public class SourcesResponse<PREV extends ModelResponse> extends ModelResponse<SourcesResponse<PREV>, PREV> {
|
||||||
super(sourcesResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SourcesResponse usingSourcesResponse() {
|
public SourcesResponse(Response response, PREV previousResponse) {
|
||||||
return new SourcesResponse(super.response);
|
super(response, previousResponse);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SourcesResponse {
|
|
||||||
|
|
||||||
private Response sourcesResponse;
|
|
||||||
|
|
||||||
public SourcesResponse(Response sourcesResponse) {
|
|
||||||
this.sourcesResponse = sourcesResponse;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SourcesResponse assertRevision(Consumer<String> assertRevision) {
|
public SourcesResponse assertRevision(Consumer<String> assertRevision) {
|
||||||
String revision = sourcesResponse.then().extract().path("revision");
|
String revision = response.then().extract().path("revision");
|
||||||
assertRevision.accept(revision);
|
assertRevision.accept(revision);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SourcesResponse assertFiles(Consumer<List> assertFiles) {
|
public SourcesResponse assertFiles(Consumer<List> assertFiles) {
|
||||||
List files = sourcesResponse.then().extract().path("files");
|
List files = response.then().extract().path("files");
|
||||||
assertFiles.accept(files);
|
assertFiles.accept(files);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AppliedChangesetsRequest requestFileHistory(String fileName) {
|
public ChangesetsResponse<SourcesResponse> requestFileHistory(String fileName) {
|
||||||
return new AppliedChangesetsRequest(applyGETRequestFromLink(sourcesResponse, "_embedded.files.find{it.name=='" + fileName + "'}._links.history.href"));
|
return new ChangesetsResponse<>(applyGETRequestFromLink(response, "_embedded.files.find{it.name=='" + fileName + "'}._links.history.href"), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AppliedSourcesRequest requestSelf(String fileName) {
|
public SourcesResponse<SourcesResponse> requestSelf(String fileName) {
|
||||||
return new AppliedSourcesRequest(applyGETRequestFromLink(sourcesResponse, "_embedded.files.find{it.name=='" + fileName + "'}._links.self.href"));
|
return new SourcesResponse<>(applyGETRequestFromLink(response, "_embedded.files.find{it.name=='" + fileName + "'}._links.self.href"), this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AppliedDiffRequest extends AppliedRequest<AppliedDiffRequest> {
|
public class ModificationsResponse<PREV extends ModelResponse> extends ModelResponse<ModificationsResponse<PREV>, PREV> {
|
||||||
|
|
||||||
public AppliedDiffRequest(Response response) {
|
public ModificationsResponse(Response response, PREV previousResponse) {
|
||||||
super(response);
|
super(response, previousResponse);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class GivenUrl {
|
|
||||||
|
|
||||||
public GivenWithUrlAndAuth usernameAndPassword(String username, String password) {
|
|
||||||
setUsername(username);
|
|
||||||
setPassword(password);
|
|
||||||
return new GivenWithUrlAndAuth();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AppliedModificationsRequest extends AppliedRequest<AppliedModificationsRequest> {
|
|
||||||
public AppliedModificationsRequest(Response response) {
|
|
||||||
super(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModificationsResponse usingModificationsResponse() {
|
public ModificationsResponse<PREV> assertRevision(Consumer<String> assertRevision) {
|
||||||
return new ModificationsResponse(super.response);
|
String revision = response.then().extract().path("revision");
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ModificationsResponse {
|
|
||||||
private Response resource;
|
|
||||||
|
|
||||||
public ModificationsResponse(Response resource) {
|
|
||||||
this.resource = resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModificationsResponse assertRevision(Consumer<String> assertRevision) {
|
|
||||||
String revision = resource.then().extract().path("revision");
|
|
||||||
assertRevision.accept(revision);
|
assertRevision.accept(revision);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModificationsResponse assertAdded(Consumer<List<String>> assertAdded) {
|
public ModificationsResponse<PREV> assertAdded(Consumer<List<String>> assertAdded) {
|
||||||
List<String> added = resource.then().extract().path("added");
|
List<String> added = response.then().extract().path("added");
|
||||||
assertAdded.accept(added);
|
assertAdded.accept(added);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModificationsResponse assertRemoved(Consumer<List<String>> assertRemoved) {
|
public ModificationsResponse<PREV> assertRemoved(Consumer<List<String>> assertRemoved) {
|
||||||
List<String> removed = resource.then().extract().path("removed");
|
List<String> removed = response.then().extract().path("removed");
|
||||||
assertRemoved.accept(removed);
|
assertRemoved.accept(removed);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModificationsResponse assertModified(Consumer<List<String>> assertModified) {
|
public ModificationsResponse<PREV> assertModified(Consumer<List<String>> assertModified) {
|
||||||
List<String> modified = resource.then().extract().path("modified");
|
List<String> modified = response.then().extract().path("modified");
|
||||||
assertModified.accept(modified);
|
assertModified.accept(modified);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AppliedMeRequest extends AppliedRequest<AppliedMeRequest> {
|
public class MeResponse<PREV extends ModelResponse> extends UserResponse<MeResponse<PREV>, PREV> {
|
||||||
|
|
||||||
public AppliedMeRequest(Response response) {
|
|
||||||
super(response);
|
public MeResponse(Response response, PREV previousResponse) {
|
||||||
|
super(response, previousResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MeResponse usingMeResponse() {
|
public ChangePasswordResponse<UserResponse> requestChangePassword(String oldPassword, String newPassword) {
|
||||||
return new MeResponse(super.response);
|
return new ChangePasswordResponse<>(applyPUTRequestFromLink(super.response, LINKS_PASSWORD_HREF, VndMediaType.PASSWORD_CHANGE, createPasswordChangeJson(oldPassword, newPassword)), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MeResponse extends UserResponse<MeResponse> {
|
public class UserResponse<SELF extends UserResponse<SELF, PREV>, PREV extends ModelResponse> extends ModelResponse<SELF, PREV> {
|
||||||
|
|
||||||
|
|
||||||
public MeResponse(Response response) {
|
|
||||||
super(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AppliedChangePasswordRequest requestChangePassword(String oldPassword, String newPassword) {
|
|
||||||
return new AppliedChangePasswordRequest(applyPUTRequestFromLink(super.response, "_links.password.href", VndMediaType.PASSWORD_CHANGE, createPasswordChangeJson(oldPassword, newPassword)));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public class UserResponse<SELF extends UserResponse> extends ModelResponse<SELF> {
|
|
||||||
|
|
||||||
public static final String LINKS_PASSWORD_HREF = "_links.password.href";
|
public static final String LINKS_PASSWORD_HREF = "_links.password.href";
|
||||||
|
|
||||||
public UserResponse(Response response) {
|
public UserResponse(Response response, PREV previousResponse) {
|
||||||
super(response);
|
super(response, previousResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SELF assertPassword(Consumer<String> assertPassword) {
|
public SELF assertPassword(Consumer<String> assertPassword) {
|
||||||
@@ -402,22 +329,27 @@ public class ScmRequests {
|
|||||||
return assertPropertyPathExists(LINKS_PASSWORD_HREF);
|
return assertPropertyPathExists(LINKS_PASSWORD_HREF);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AppliedChangePasswordRequest requestChangePassword(String newPassword) {
|
public ChangePasswordResponse<UserResponse> requestChangePassword(String newPassword) {
|
||||||
return new AppliedChangePasswordRequest(applyPUTRequestFromLink(super.response, LINKS_PASSWORD_HREF, VndMediaType.PASSWORD_CHANGE, createPasswordChangeJson(null, newPassword)));
|
return new ChangePasswordResponse<>(applyPUTRequestFromLink(super.response, LINKS_PASSWORD_HREF, VndMediaType.PASSWORD_OVERWRITE, createPasswordChangeJson(null, newPassword)), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* encapsulate standard assertions over model properties
|
* encapsulate standard assertions over model properties
|
||||||
*/
|
*/
|
||||||
public class ModelResponse<SELF extends ModelResponse> {
|
public class ModelResponse<SELF extends ModelResponse<SELF, PREV>, PREV extends ModelResponse> {
|
||||||
|
|
||||||
|
protected PREV previousResponse;
|
||||||
protected Response response;
|
protected Response response;
|
||||||
|
|
||||||
public ModelResponse(Response response) {
|
public ModelResponse(Response response, PREV previousResponse) {
|
||||||
this.response = response;
|
this.response = response;
|
||||||
|
this.previousResponse = previousResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PREV returnToPrevious() {
|
||||||
|
return previousResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> SELF assertSingleProperty(Consumer<T> assertSingleProperty, String propertyJsonPath) {
|
public <T> SELF assertSingleProperty(Consumer<T> assertSingleProperty, String propertyJsonPath) {
|
||||||
@@ -441,25 +373,45 @@ public class ScmRequests {
|
|||||||
assertProperties.accept(properties);
|
assertProperties.accept(properties);
|
||||||
return (SELF) this;
|
return (SELF) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* special assertion of the status code
|
||||||
|
*
|
||||||
|
* @param expectedStatusCode the expected status code
|
||||||
|
* @return the self object
|
||||||
|
*/
|
||||||
|
public SELF assertStatusCode(int expectedStatusCode) {
|
||||||
|
this.response.then().assertThat().statusCode(expectedStatusCode);
|
||||||
|
return (SELF) this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AppliedChangePasswordRequest extends AppliedRequest<AppliedChangePasswordRequest> {
|
public class AutoCompleteResponse<PREV extends ModelResponse> extends ModelResponse<AutoCompleteResponse<PREV>, PREV> {
|
||||||
|
|
||||||
public AppliedChangePasswordRequest(Response response) {
|
public AutoCompleteResponse(Response response, PREV previousResponse) {
|
||||||
super(response);
|
super(response, previousResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AutoCompleteResponse<PREV> assertAutoCompleteResults(Consumer<List<Map>> checker) {
|
||||||
|
List<Map> result = response.then().extract().path("");
|
||||||
|
checker.accept(result);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AppliedUserRequest extends AppliedRequest<AppliedUserRequest> {
|
|
||||||
|
|
||||||
public AppliedUserRequest(Response response) {
|
public class DiffResponse<PREV extends ModelResponse> extends ModelResponse<DiffResponse<PREV>, PREV> {
|
||||||
super(response);
|
|
||||||
|
public DiffResponse(Response response, PREV previousResponse) {
|
||||||
|
super(response, previousResponse);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public UserResponse usingUserResponse() {
|
public class ChangePasswordResponse<PREV extends ModelResponse> extends ModelResponse<ChangePasswordResponse<PREV>, PREV> {
|
||||||
return new UserResponse(super.response);
|
|
||||||
|
public ChangePasswordResponse(Response response, PREV previousResponse) {
|
||||||
|
super(response, previousResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,11 +46,11 @@ public class TestData {
|
|||||||
return DEFAULT_REPOSITORIES.get(repositoryType);
|
return DEFAULT_REPOSITORIES.get(repositoryType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void createUser(String username, String password) {
|
public static void createNotAdminUser(String username, String password) {
|
||||||
createUser(username, password, false, "xml");
|
createUser(username, password, false, "xml", "user1@scm-manager.org");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void createUser(String username, String password, boolean isAdmin, String type) {
|
public static void createUser(String username, String password, boolean isAdmin, String type, final String email) {
|
||||||
LOG.info("create user with username: {}", username);
|
LOG.info("create user with username: {}", username);
|
||||||
String admin = isAdmin ? "true" : "false";
|
String admin = isAdmin ? "true" : "false";
|
||||||
given(VndMediaType.USER)
|
given(VndMediaType.USER)
|
||||||
@@ -61,7 +61,7 @@ public class TestData {
|
|||||||
.append(" \"admin\": ").append(admin).append(",\n")
|
.append(" \"admin\": ").append(admin).append(",\n")
|
||||||
.append(" \"creationDate\": \"2018-08-21T12:26:46.084Z\",\n")
|
.append(" \"creationDate\": \"2018-08-21T12:26:46.084Z\",\n")
|
||||||
.append(" \"displayName\": \"").append(username).append("\",\n")
|
.append(" \"displayName\": \"").append(username).append("\",\n")
|
||||||
.append(" \"mail\": \"user1@scm-manager.org\",\n")
|
.append(" \"mail\": \"" + email + "\",\n")
|
||||||
.append(" \"name\": \"").append(username).append("\",\n")
|
.append(" \"name\": \"").append(username).append("\",\n")
|
||||||
.append(" \"password\": \"").append(password).append("\",\n")
|
.append(" \"password\": \"").append(password).append("\",\n")
|
||||||
.append(" \"type\": \"").append(type).append("\"\n")
|
.append(" \"type\": \"").append(type).append("\"\n")
|
||||||
@@ -71,6 +71,16 @@ public class TestData {
|
|||||||
.statusCode(HttpStatus.SC_CREATED)
|
.statusCode(HttpStatus.SC_CREATED)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
public static void createGroup(String groupName, String desc) {
|
||||||
|
LOG.info("create group with group name: {} and description {}", groupName, desc);
|
||||||
|
given(VndMediaType.GROUP)
|
||||||
|
.when()
|
||||||
|
.content(getGroupJson(groupName,desc))
|
||||||
|
.post(getGroupsUrl())
|
||||||
|
.then()
|
||||||
|
.statusCode(HttpStatus.SC_CREATED)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
public static void createUserPermission(String name, PermissionType permissionType, String repositoryType) {
|
public static void createUserPermission(String name, PermissionType permissionType, String repositoryType) {
|
||||||
String defaultPermissionUrl = TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType);
|
String defaultPermissionUrl = TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType);
|
||||||
@@ -193,28 +203,31 @@ public class TestData {
|
|||||||
return JSON_BUILDER
|
return JSON_BUILDER
|
||||||
.add("contact", "zaphod.beeblebrox@hitchhiker.com")
|
.add("contact", "zaphod.beeblebrox@hitchhiker.com")
|
||||||
.add("description", "Heart of Gold")
|
.add("description", "Heart of Gold")
|
||||||
.add("name", "HeartOfGold-" + repositoryType)
|
.add("name", getDefaultRepoName(repositoryType))
|
||||||
.add("archived", false)
|
.add("archived", false)
|
||||||
.add("type", repositoryType)
|
.add("type", repositoryType)
|
||||||
.build().toString();
|
.build().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static URI getMeUrl() {
|
public static String getDefaultRepoName(String repositoryType) {
|
||||||
return RestUtil.createResourceUrl("me/");
|
return "HeartOfGold-" + repositoryType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getGroupJson(String groupname , String desc) {
|
||||||
|
return JSON_BUILDER
|
||||||
|
.add("name", groupname)
|
||||||
|
.add("description", desc)
|
||||||
|
.build().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static URI getGroupsUrl() {
|
||||||
|
return RestUtil.createResourceUrl("groups/");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static URI getUsersUrl() {
|
public static URI getUsersUrl() {
|
||||||
return RestUtil.createResourceUrl("users/");
|
return RestUtil.createResourceUrl("users/");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static URI getUserUrl(String username) {
|
|
||||||
return getUsersUrl().resolve(username);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static String createPasswordChangeJson(String oldPassword, String newPassword) {
|
public static String createPasswordChangeJson(String oldPassword, String newPassword) {
|
||||||
return JSON_BUILDER
|
return JSON_BUILDER
|
||||||
.add("oldPassword", oldPassword)
|
.add("oldPassword", oldPassword)
|
||||||
@@ -225,4 +238,5 @@ public class TestData {
|
|||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,6 @@
|
|||||||
"@scm-manager/ui-extensions": "^0.0.7"
|
"@scm-manager/ui-extensions": "^0.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.15"
|
"@scm-manager/ui-bundler": "^0.0.17"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -707,9 +707,9 @@
|
|||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
||||||
|
|
||||||
"@scm-manager/ui-bundler@^0.0.15":
|
"@scm-manager/ui-bundler@^0.0.17":
|
||||||
version "0.0.15"
|
version "0.0.17"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.15.tgz#8ed4a557d5ae38d6b99493b29608fd6a4c9cd917"
|
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.17.tgz#949b90ca57e4268be28fcf4975bd9622f60278bb"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.0.0"
|
"@babel/core" "^7.0.0"
|
||||||
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
||||||
@@ -726,7 +726,6 @@
|
|||||||
browserify-css "^0.14.0"
|
browserify-css "^0.14.0"
|
||||||
colors "^1.3.1"
|
colors "^1.3.1"
|
||||||
commander "^2.17.1"
|
commander "^2.17.1"
|
||||||
connect-history-api-fallback "^1.5.0"
|
|
||||||
eslint "^5.4.0"
|
eslint "^5.4.0"
|
||||||
eslint-config-react-app "^2.1.0"
|
eslint-config-react-app "^2.1.0"
|
||||||
eslint-plugin-flowtype "^2.50.0"
|
eslint-plugin-flowtype "^2.50.0"
|
||||||
|
|||||||
@@ -9,6 +9,6 @@
|
|||||||
"@scm-manager/ui-extensions": "^0.0.7"
|
"@scm-manager/ui-extensions": "^0.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.15"
|
"@scm-manager/ui-bundler": "^0.0.17"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -641,9 +641,9 @@
|
|||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
||||||
|
|
||||||
"@scm-manager/ui-bundler@^0.0.15":
|
"@scm-manager/ui-bundler@^0.0.17":
|
||||||
version "0.0.15"
|
version "0.0.17"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.15.tgz#8ed4a557d5ae38d6b99493b29608fd6a4c9cd917"
|
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.17.tgz#949b90ca57e4268be28fcf4975bd9622f60278bb"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.0.0"
|
"@babel/core" "^7.0.0"
|
||||||
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
||||||
@@ -660,7 +660,6 @@
|
|||||||
browserify-css "^0.14.0"
|
browserify-css "^0.14.0"
|
||||||
colors "^1.3.1"
|
colors "^1.3.1"
|
||||||
commander "^2.17.1"
|
commander "^2.17.1"
|
||||||
connect-history-api-fallback "^1.5.0"
|
|
||||||
eslint "^5.4.0"
|
eslint "^5.4.0"
|
||||||
eslint-config-react-app "^2.1.0"
|
eslint-config-react-app "^2.1.0"
|
||||||
eslint-plugin-flowtype "^2.50.0"
|
eslint-plugin-flowtype "^2.50.0"
|
||||||
|
|||||||
@@ -9,6 +9,6 @@
|
|||||||
"@scm-manager/ui-extensions": "^0.0.7"
|
"@scm-manager/ui-extensions": "^0.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.15"
|
"@scm-manager/ui-bundler": "^0.0.17"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -641,9 +641,9 @@
|
|||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
||||||
|
|
||||||
"@scm-manager/ui-bundler@^0.0.15":
|
"@scm-manager/ui-bundler@^0.0.17":
|
||||||
version "0.0.15"
|
version "0.0.17"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.15.tgz#8ed4a557d5ae38d6b99493b29608fd6a4c9cd917"
|
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.17.tgz#949b90ca57e4268be28fcf4975bd9622f60278bb"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.0.0"
|
"@babel/core" "^7.0.0"
|
||||||
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
||||||
@@ -660,7 +660,6 @@
|
|||||||
browserify-css "^0.14.0"
|
browserify-css "^0.14.0"
|
||||||
colors "^1.3.1"
|
colors "^1.3.1"
|
||||||
commander "^2.17.1"
|
commander "^2.17.1"
|
||||||
connect-history-api-fallback "^1.5.0"
|
|
||||||
eslint "^5.4.0"
|
eslint "^5.4.0"
|
||||||
eslint-config-react-app "^2.1.0"
|
eslint-config-react-app "^2.1.0"
|
||||||
eslint-plugin-flowtype "^2.50.0"
|
eslint-plugin-flowtype "^2.50.0"
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ public abstract class UserManagerTestBase extends ManagerTestBase<User> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = NotFoundException.class)
|
@Test(expected = NotFoundException.class)
|
||||||
public void testModifyNotExisting() throws NotFoundException, ConcurrentModificationException {
|
public void testModifyNotExisting() {
|
||||||
manager.modify(UserTestData.createZaphod());
|
manager.modify(UserTestData.createZaphod());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,7 +249,7 @@ public abstract class UserManagerTestBase extends ManagerTestBase<User> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = NotFoundException.class)
|
@Test(expected = NotFoundException.class)
|
||||||
public void testRefreshNotFound() throws NotFoundException {
|
public void testRefreshNotFound(){
|
||||||
manager.refresh(UserTestData.createDent());
|
manager.refresh(UserTestData.createDent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"bootstrap": "lerna bootstrap",
|
"bootstrap": "lerna bootstrap",
|
||||||
"link": "lerna exec -- yarn link",
|
"link": "lerna exec -- yarn link",
|
||||||
"unlink": "lerna exec --no-bail -- yarn unlink"
|
"unlink": "lerna exec --no-bail -- yarn unlink || true"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"lerna": "^3.2.1"
|
"lerna": "^3.2.1"
|
||||||
|
|||||||
@@ -12,20 +12,21 @@
|
|||||||
"eslint-fix": "eslint src --fix"
|
"eslint-fix": "eslint src --fix"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.15",
|
"@scm-manager/ui-bundler": "^0.0.17",
|
||||||
"create-index": "^2.3.0",
|
"create-index": "^2.3.0",
|
||||||
"enzyme": "^3.5.0",
|
"enzyme": "^3.5.0",
|
||||||
"enzyme-adapter-react-16": "^1.3.1",
|
"enzyme-adapter-react-16": "^1.3.1",
|
||||||
"flow-bin": "^0.79.1",
|
"flow-bin": "^0.79.1",
|
||||||
"flow-typed": "^2.5.1",
|
"flow-typed": "^2.5.1",
|
||||||
"jest": "^23.5.0",
|
"jest": "^23.5.0",
|
||||||
"raf": "^3.4.0"
|
"raf": "^3.4.0",
|
||||||
|
"react-router-enzyme-context": "^1.2.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"moment": "^2.22.2",
|
"moment": "^2.22.2",
|
||||||
"react": "^16.4.2",
|
"react": "^16.5.2",
|
||||||
"react-dom": "^16.4.2",
|
"react-dom": "^16.5.2",
|
||||||
"react-i18next": "^7.11.0",
|
"react-i18next": "^7.11.0",
|
||||||
"react-jss": "^8.6.1",
|
"react-jss": "^8.6.1",
|
||||||
"react-router-dom": "^4.3.1",
|
"react-router-dom": "^4.3.1",
|
||||||
|
|||||||
133
scm-ui-components/packages/ui-components/src/LinkPaginator.js
Normal file
133
scm-ui-components/packages/ui-components/src/LinkPaginator.js
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
//@flow
|
||||||
|
import React from "react";
|
||||||
|
import {translate} from "react-i18next";
|
||||||
|
import type {PagedCollection} from "@scm-manager/ui-types";
|
||||||
|
import {Button} from "./buttons";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
collection: PagedCollection,
|
||||||
|
page: number,
|
||||||
|
|
||||||
|
// context props
|
||||||
|
t: string => string
|
||||||
|
};
|
||||||
|
|
||||||
|
class LinkPaginator extends React.Component<Props> {
|
||||||
|
|
||||||
|
renderFirstButton() {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={"pagination-link"}
|
||||||
|
label={"1"}
|
||||||
|
disabled={false}
|
||||||
|
link={"1"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPreviousButton(label?: string) {
|
||||||
|
const { page } = this.props;
|
||||||
|
const previousPage = page - 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={"pagination-previous"}
|
||||||
|
label={label ? label : previousPage.toString()}
|
||||||
|
disabled={!this.hasLink("prev")}
|
||||||
|
link={`${previousPage}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasLink(name: string) {
|
||||||
|
const { collection } = this.props;
|
||||||
|
return collection._links[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
renderNextButton(label?: string) {
|
||||||
|
const { page } = this.props;
|
||||||
|
const nextPage = page + 1;
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={"pagination-next"}
|
||||||
|
label={label ? label : nextPage.toString()}
|
||||||
|
disabled={!this.hasLink("next")}
|
||||||
|
link={`${nextPage}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderLastButton() {
|
||||||
|
const { collection } = this.props;
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={"pagination-link"}
|
||||||
|
label={`${collection.pageTotal}`}
|
||||||
|
disabled={false}
|
||||||
|
link={`${collection.pageTotal}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
separator() {
|
||||||
|
return <span className="pagination-ellipsis">…</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPage(page: number) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className="pagination-link is-current"
|
||||||
|
label={page}
|
||||||
|
disabled={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pageLinks() {
|
||||||
|
const { collection } = this.props;
|
||||||
|
|
||||||
|
const links = [];
|
||||||
|
const page = collection.page + 1;
|
||||||
|
const pageTotal = collection.pageTotal;
|
||||||
|
if (page > 1) {
|
||||||
|
links.push(this.renderFirstButton());
|
||||||
|
}
|
||||||
|
if (page > 3) {
|
||||||
|
links.push(this.separator());
|
||||||
|
}
|
||||||
|
if (page > 2) {
|
||||||
|
links.push(this.renderPreviousButton());
|
||||||
|
}
|
||||||
|
|
||||||
|
links.push(this.currentPage(page));
|
||||||
|
|
||||||
|
if (page + 1 < pageTotal) {
|
||||||
|
links.push(this.renderNextButton());
|
||||||
|
}
|
||||||
|
if (page + 2 < pageTotal)
|
||||||
|
//if there exists pages between next and last
|
||||||
|
links.push(this.separator());
|
||||||
|
if (page < pageTotal) {
|
||||||
|
links.push(this.renderLastButton());
|
||||||
|
}
|
||||||
|
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { t } = this.props;
|
||||||
|
return (
|
||||||
|
<nav className="pagination is-centered" aria-label="pagination">
|
||||||
|
{this.renderPreviousButton(t("paginator.previous"))}
|
||||||
|
<ul className="pagination-list">
|
||||||
|
{this.pageLinks().map((link, index) => {
|
||||||
|
return <li key={index}>{link}</li>;
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
{this.renderNextButton(t("paginator.next"))}
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate("commons")(LinkPaginator);
|
||||||
@@ -18,8 +18,10 @@ class Paginator extends React.Component<Props> {
|
|||||||
createAction = (linkType: string) => () => {
|
createAction = (linkType: string) => () => {
|
||||||
const { collection, onPageChange } = this.props;
|
const { collection, onPageChange } = this.props;
|
||||||
if (onPageChange) {
|
if (onPageChange) {
|
||||||
const link = collection._links[linkType].href;
|
const link = collection._links[linkType];
|
||||||
onPageChange(link);
|
if (link && link.href) {
|
||||||
|
onPageChange(link.href);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,13 @@ import React from "react";
|
|||||||
import { mount, shallow } from "enzyme";
|
import { mount, shallow } from "enzyme";
|
||||||
import "./tests/enzyme";
|
import "./tests/enzyme";
|
||||||
import "./tests/i18n";
|
import "./tests/i18n";
|
||||||
|
import ReactRouterEnzymeContext from "react-router-enzyme-context";
|
||||||
import Paginator from "./Paginator";
|
import Paginator from "./Paginator";
|
||||||
|
|
||||||
describe("paginator rendering tests", () => {
|
describe("paginator rendering tests", () => {
|
||||||
|
|
||||||
|
const options = new ReactRouterEnzymeContext();
|
||||||
|
|
||||||
const dummyLink = {
|
const dummyLink = {
|
||||||
href: "https://dummy"
|
href: "https://dummy"
|
||||||
};
|
};
|
||||||
@@ -18,7 +21,10 @@ describe("paginator rendering tests", () => {
|
|||||||
_links: {}
|
_links: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
const paginator = shallow(<Paginator collection={collection} />);
|
const paginator = shallow(
|
||||||
|
<Paginator collection={collection} />,
|
||||||
|
options.get()
|
||||||
|
);
|
||||||
const buttons = paginator.find("Button");
|
const buttons = paginator.find("Button");
|
||||||
expect(buttons.length).toBe(7);
|
expect(buttons.length).toBe(7);
|
||||||
for (let button of buttons) {
|
for (let button of buttons) {
|
||||||
@@ -37,7 +43,10 @@ describe("paginator rendering tests", () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const paginator = shallow(<Paginator collection={collection} />);
|
const paginator = shallow(
|
||||||
|
<Paginator collection={collection} />,
|
||||||
|
options.get()
|
||||||
|
);
|
||||||
const buttons = paginator.find("Button");
|
const buttons = paginator.find("Button");
|
||||||
expect(buttons.length).toBe(5);
|
expect(buttons.length).toBe(5);
|
||||||
|
|
||||||
@@ -73,7 +82,10 @@ describe("paginator rendering tests", () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const paginator = shallow(<Paginator collection={collection} />);
|
const paginator = shallow(
|
||||||
|
<Paginator collection={collection} />,
|
||||||
|
options.get()
|
||||||
|
);
|
||||||
const buttons = paginator.find("Button");
|
const buttons = paginator.find("Button");
|
||||||
expect(buttons.length).toBe(6);
|
expect(buttons.length).toBe(6);
|
||||||
|
|
||||||
@@ -112,7 +124,10 @@ describe("paginator rendering tests", () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const paginator = shallow(<Paginator collection={collection} />);
|
const paginator = shallow(
|
||||||
|
<Paginator collection={collection} />,
|
||||||
|
options.get()
|
||||||
|
);
|
||||||
const buttons = paginator.find("Button");
|
const buttons = paginator.find("Button");
|
||||||
expect(buttons.length).toBe(5);
|
expect(buttons.length).toBe(5);
|
||||||
|
|
||||||
@@ -148,7 +163,10 @@ describe("paginator rendering tests", () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const paginator = shallow(<Paginator collection={collection} />);
|
const paginator = shallow(
|
||||||
|
<Paginator collection={collection} />,
|
||||||
|
options.get()
|
||||||
|
);
|
||||||
const buttons = paginator.find("Button");
|
const buttons = paginator.find("Button");
|
||||||
expect(buttons.length).toBe(6);
|
expect(buttons.length).toBe(6);
|
||||||
|
|
||||||
@@ -189,7 +207,10 @@ describe("paginator rendering tests", () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const paginator = shallow(<Paginator collection={collection} />);
|
const paginator = shallow(
|
||||||
|
<Paginator collection={collection} />,
|
||||||
|
options.get()
|
||||||
|
);
|
||||||
const buttons = paginator.find("Button");
|
const buttons = paginator.find("Button");
|
||||||
expect(buttons.length).toBe(7);
|
expect(buttons.length).toBe(7);
|
||||||
|
|
||||||
@@ -244,7 +265,8 @@ describe("paginator rendering tests", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const paginator = mount(
|
const paginator = mount(
|
||||||
<Paginator collection={collection} onPageChange={callMe} />
|
<Paginator collection={collection} onPageChange={callMe} />,
|
||||||
|
options.get()
|
||||||
);
|
);
|
||||||
paginator.find("Button.pagination-previous").simulate("click");
|
paginator.find("Button.pagination-previous").simulate("click");
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Link } from "react-router-dom";
|
import { withRouter } from "react-router-dom";
|
||||||
|
|
||||||
export type ButtonProps = {
|
export type ButtonProps = {
|
||||||
label: string,
|
label: string,
|
||||||
@@ -16,7 +16,10 @@ export type ButtonProps = {
|
|||||||
|
|
||||||
type Props = ButtonProps & {
|
type Props = ButtonProps & {
|
||||||
type: string,
|
type: string,
|
||||||
color: string
|
color: string,
|
||||||
|
|
||||||
|
// context prop
|
||||||
|
history: any
|
||||||
};
|
};
|
||||||
|
|
||||||
class Button extends React.Component<Props> {
|
class Button extends React.Component<Props> {
|
||||||
@@ -25,14 +28,22 @@ class Button extends React.Component<Props> {
|
|||||||
color: "default"
|
color: "default"
|
||||||
};
|
};
|
||||||
|
|
||||||
renderButton = () => {
|
onClick = (event: Event) => {
|
||||||
|
const { action, link, history } = this.props;
|
||||||
|
if (action) {
|
||||||
|
action(event);
|
||||||
|
} else if (link) {
|
||||||
|
history.push(link);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
const {
|
const {
|
||||||
label,
|
label,
|
||||||
loading,
|
loading,
|
||||||
disabled,
|
disabled,
|
||||||
type,
|
type,
|
||||||
color,
|
color,
|
||||||
action,
|
|
||||||
fullWidth,
|
fullWidth,
|
||||||
className
|
className
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -42,7 +53,7 @@ class Button extends React.Component<Props> {
|
|||||||
<button
|
<button
|
||||||
type={type}
|
type={type}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={action ? action : (event: Event) => {}}
|
onClick={this.onClick}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"button",
|
"button",
|
||||||
"is-" + color,
|
"is-" + color,
|
||||||
@@ -56,14 +67,6 @@ class Button extends React.Component<Props> {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
|
||||||
const { link } = this.props;
|
|
||||||
if (link) {
|
|
||||||
return <Link to={link}>{this.renderButton()}</Link>;
|
|
||||||
} else {
|
|
||||||
return this.renderButton();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Button;
|
export default withRouter(Button);
|
||||||
|
|||||||
@@ -15,9 +15,11 @@ export { default as Logo } from "./Logo.js";
|
|||||||
export { default as MailLink } from "./MailLink.js";
|
export { default as MailLink } from "./MailLink.js";
|
||||||
export { default as Notification } from "./Notification.js";
|
export { default as Notification } from "./Notification.js";
|
||||||
export { default as Paginator } from "./Paginator.js";
|
export { default as Paginator } from "./Paginator.js";
|
||||||
|
export { default as LinkPaginator } from "./LinkPaginator.js";
|
||||||
export { default as ProtectedRoute } from "./ProtectedRoute.js";
|
export { default as ProtectedRoute } from "./ProtectedRoute.js";
|
||||||
export { default as Help } from "./Help.js";
|
export { default as Help } from "./Help.js";
|
||||||
export { default as LabelWithHelpIcon } from "./LabelWithHelpIcon.js";
|
export { default as LabelWithHelpIcon } from "./LabelWithHelpIcon.js";
|
||||||
|
export { getPageFromMatch } from "./urls";
|
||||||
|
|
||||||
export { apiClient, NOT_FOUND_ERROR, UNAUTHORIZED_ERROR } from "./apiclient.js";
|
export { apiClient, NOT_FOUND_ERROR, UNAUTHORIZED_ERROR } from "./apiclient.js";
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import { Route, Link } from "react-router-dom";
|
|||||||
type Props = {
|
type Props = {
|
||||||
to: string,
|
to: string,
|
||||||
label: string,
|
label: string,
|
||||||
activeOnlyWhenExact?: boolean
|
activeOnlyWhenExact?: boolean,
|
||||||
|
activeWhenMatch?: (route: any) => boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
class NavLink extends React.Component<Props> {
|
class NavLink extends React.Component<Props> {
|
||||||
@@ -15,11 +16,17 @@ class NavLink extends React.Component<Props> {
|
|||||||
activeOnlyWhenExact: true
|
activeOnlyWhenExact: true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
isActive(route: any) {
|
||||||
|
const { activeWhenMatch } = this.props;
|
||||||
|
return route.match || (activeWhenMatch && activeWhenMatch(route));
|
||||||
|
}
|
||||||
|
|
||||||
renderLink = (route: any) => {
|
renderLink = (route: any) => {
|
||||||
const { to, label } = this.props;
|
const { to, label } = this.props;
|
||||||
return (
|
return (
|
||||||
<li>
|
<li>
|
||||||
<Link className={route.match ? "is-active" : ""} to={to}>
|
<Link className={this.isActive(route) ? "is-active" : ""} to={to}>
|
||||||
{label}
|
{label}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import type { Repository } from "@scm-manager/ui-types";
|
import type { Repository } from "@scm-manager/ui-types";
|
||||||
import { getProtocolLinkByType, getTypePredicate } from "./repositories";
|
import { getProtocolLinkByType } from "./repositories";
|
||||||
|
|
||||||
describe("getProtocolLinkByType tests", () => {
|
describe("getProtocolLinkByType tests", () => {
|
||||||
|
|
||||||
|
|||||||
@@ -4,3 +4,11 @@ export const contextPath = window.ctxPath || "";
|
|||||||
export function withContextPath(path: string) {
|
export function withContextPath(path: string) {
|
||||||
return contextPath + path;
|
return contextPath + path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPageFromMatch(match: any) {
|
||||||
|
let page = parseInt(match.params.page, 10);
|
||||||
|
if (isNaN(page) || !page) {
|
||||||
|
page = 1;
|
||||||
|
}
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|||||||
27
scm-ui-components/packages/ui-components/src/urls.test.js
Normal file
27
scm-ui-components/packages/ui-components/src/urls.test.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// @flow
|
||||||
|
import { getPageFromMatch } from "./urls";
|
||||||
|
|
||||||
|
describe("tests for getPageFromMatch", () => {
|
||||||
|
function createMatch(page: string) {
|
||||||
|
return {
|
||||||
|
params: {
|
||||||
|
page
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should return 1 for NaN", () => {
|
||||||
|
const match = createMatch("any");
|
||||||
|
expect(getPageFromMatch(match)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return 1 for 0", () => {
|
||||||
|
const match = createMatch("0");
|
||||||
|
expect(getPageFromMatch(match)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the given number", () => {
|
||||||
|
const match = createMatch("42");
|
||||||
|
expect(getPageFromMatch(match)).toBe(42);
|
||||||
|
});
|
||||||
|
});
|
||||||
File diff suppressed because it is too large
Load Diff
8011
scm-ui-components/packages/ui-components/yarn.lock
Normal file
8011
scm-ui-components/packages/ui-components/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@
|
|||||||
"check": "flow check"
|
"check": "flow check"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.15"
|
"@scm-manager/ui-bundler": "^0.0.17"
|
||||||
},
|
},
|
||||||
"browserify": {
|
"browserify": {
|
||||||
"transform": [
|
"transform": [
|
||||||
|
|||||||
8
scm-ui-components/packages/ui-types/src/Branches.js
Normal file
8
scm-ui-components/packages/ui-types/src/Branches.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
//@flow
|
||||||
|
import type {Links} from "./hal";
|
||||||
|
|
||||||
|
export type Branch = {
|
||||||
|
name: string,
|
||||||
|
revision: string,
|
||||||
|
_links: Links
|
||||||
|
}
|
||||||
25
scm-ui-components/packages/ui-types/src/Changesets.js
Normal file
25
scm-ui-components/packages/ui-types/src/Changesets.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
//@flow
|
||||||
|
import type {Links} from "./hal";
|
||||||
|
import type {Tag} from "./Tags";
|
||||||
|
import type {Branch} from "./Branches";
|
||||||
|
|
||||||
|
export type Changeset = {
|
||||||
|
id: string,
|
||||||
|
date: Date,
|
||||||
|
author: {
|
||||||
|
name: string,
|
||||||
|
mail?: string
|
||||||
|
},
|
||||||
|
description: string,
|
||||||
|
_links: Links,
|
||||||
|
_embedded: {
|
||||||
|
tags?: Tag[],
|
||||||
|
branches?: Branch[],
|
||||||
|
parents?: ParentChangeset[]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ParentChangeset = {
|
||||||
|
id: string,
|
||||||
|
_links: Links
|
||||||
|
}
|
||||||
8
scm-ui-components/packages/ui-types/src/Tags.js
Normal file
8
scm-ui-components/packages/ui-types/src/Tags.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
//@flow
|
||||||
|
import type { Links } from "./hal";
|
||||||
|
|
||||||
|
export type Tag = {
|
||||||
|
name: string,
|
||||||
|
revision: string,
|
||||||
|
_links: Links
|
||||||
|
}
|
||||||
@@ -4,10 +4,14 @@ export type Link = {
|
|||||||
name?: string
|
name?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Links = { [string]: Link | Link[] };
|
type LinkValue = Link | Link[];
|
||||||
|
|
||||||
|
// TODO use LinkValue
|
||||||
|
export type Links = { [string]: any };
|
||||||
|
|
||||||
export type Collection = {
|
export type Collection = {
|
||||||
_embedded: Object,
|
_embedded: Object,
|
||||||
|
// $FlowFixMe
|
||||||
_links: Links
|
_links: Links
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ export type { Group, Member } from "./Group";
|
|||||||
export type { Repository, RepositoryCollection, RepositoryGroup } from "./Repositories";
|
export type { Repository, RepositoryCollection, RepositoryGroup } from "./Repositories";
|
||||||
export type { RepositoryType, RepositoryTypeCollection } from "./RepositoryTypes";
|
export type { RepositoryType, RepositoryTypeCollection } from "./RepositoryTypes";
|
||||||
|
|
||||||
|
export type { Branch } from "./Branches";
|
||||||
|
|
||||||
|
export type { Changeset } from "./Changesets";
|
||||||
|
|
||||||
|
export type { Tag } from "./Tags";
|
||||||
|
|
||||||
export type { Config } from "./Config";
|
export type { Config } from "./Config";
|
||||||
|
|
||||||
export type { Permission, PermissionEntry, PermissionCollection } from "./RepositoryPermissions";
|
export type { Permission, PermissionEntry, PermissionCollection } from "./RepositoryPermissions";
|
||||||
|
|||||||
@@ -707,9 +707,9 @@
|
|||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
||||||
|
|
||||||
"@scm-manager/ui-bundler@^0.0.15":
|
"@scm-manager/ui-bundler@^0.0.17":
|
||||||
version "0.0.15"
|
version "0.0.17"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.15.tgz#8ed4a557d5ae38d6b99493b29608fd6a4c9cd917"
|
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.17.tgz#949b90ca57e4268be28fcf4975bd9622f60278bb"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.0.0"
|
"@babel/core" "^7.0.0"
|
||||||
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
||||||
@@ -726,7 +726,6 @@
|
|||||||
browserify-css "^0.14.0"
|
browserify-css "^0.14.0"
|
||||||
colors "^1.3.1"
|
colors "^1.3.1"
|
||||||
commander "^2.17.1"
|
commander "^2.17.1"
|
||||||
connect-history-api-fallback "^1.5.0"
|
|
||||||
eslint "^5.4.0"
|
eslint "^5.4.0"
|
||||||
eslint-config-react-app "^2.1.0"
|
eslint-config-react-app "^2.1.0"
|
||||||
eslint-plugin-flowtype "^2.50.0"
|
eslint-plugin-flowtype "^2.50.0"
|
||||||
|
|||||||
@@ -2451,8 +2451,8 @@ npm-lifecycle@^2.0.0:
|
|||||||
validate-npm-package-name "^3.0.0"
|
validate-npm-package-name "^3.0.0"
|
||||||
|
|
||||||
npm-packlist@^1.1.10:
|
npm-packlist@^1.1.10:
|
||||||
version "1.1.12"
|
version "1.1.11"
|
||||||
resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.12.tgz#22bde2ebc12e72ca482abd67afc51eb49377243a"
|
resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.11.tgz#84e8c683cbe7867d34b1d357d893ce29e28a02de"
|
||||||
dependencies:
|
dependencies:
|
||||||
ignore-walk "^3.0.1"
|
ignore-walk "^3.0.1"
|
||||||
npm-bundled "^1.0.1"
|
npm-bundled "^1.0.1"
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
"i18next-browser-languagedetector": "^2.2.2",
|
"i18next-browser-languagedetector": "^2.2.2",
|
||||||
"i18next-fetch-backend": "^0.1.0",
|
"i18next-fetch-backend": "^0.1.0",
|
||||||
"moment": "^2.22.2",
|
"moment": "^2.22.2",
|
||||||
|
"react": "^16.5.2",
|
||||||
|
"react-dom": "^16.5.2",
|
||||||
"node-sass": "^4.9.3",
|
"node-sass": "^4.9.3",
|
||||||
"postcss-easy-import": "^3.0.0",
|
"postcss-easy-import": "^3.0.0",
|
||||||
"react": "^16.4.2",
|
"react": "^16.4.2",
|
||||||
@@ -46,7 +48,7 @@
|
|||||||
"pre-commit": "jest && flow && eslint src"
|
"pre-commit": "jest && flow && eslint src"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.15",
|
"@scm-manager/ui-bundler": "^0.0.17",
|
||||||
"copyfiles": "^2.0.0",
|
"copyfiles": "^2.0.0",
|
||||||
"enzyme": "^3.3.0",
|
"enzyme": "^3.3.0",
|
||||||
"enzyme-adapter-react-16": "^1.1.1",
|
"enzyme-adapter-react-16": "^1.1.1",
|
||||||
@@ -54,6 +56,7 @@
|
|||||||
"flow-typed": "^2.5.1",
|
"flow-typed": "^2.5.1",
|
||||||
"jest": "^23.5.0",
|
"jest": "^23.5.0",
|
||||||
"postcss-easy-import": "^3.0.0",
|
"postcss-easy-import": "^3.0.0",
|
||||||
|
"node-sass": "^4.9.3",
|
||||||
"node-sass-chokidar": "^1.3.0",
|
"node-sass-chokidar": "^1.3.0",
|
||||||
"npm-run-all": "^4.1.3",
|
"npm-run-all": "^4.1.3",
|
||||||
"prettier": "^1.13.7",
|
"prettier": "^1.13.7",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
"actions-label": "Actions",
|
"actions-label": "Actions",
|
||||||
"back-label": "Back",
|
"back-label": "Back",
|
||||||
"navigation-label": "Navigation",
|
"navigation-label": "Navigation",
|
||||||
|
"history": "Commits",
|
||||||
"information": "Information",
|
"information": "Information",
|
||||||
"permissions": "Permissions"
|
"permissions": "Permissions"
|
||||||
},
|
},
|
||||||
@@ -44,31 +45,49 @@
|
|||||||
"cancel": "No"
|
"cancel": "No"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"changesets": {
|
||||||
|
"error-title": "Error",
|
||||||
|
"error-subtitle": "Could not fetch changesets",
|
||||||
|
"changeset": {
|
||||||
|
"id": "ID",
|
||||||
|
"description": "Description",
|
||||||
|
"contact": "Contact",
|
||||||
|
"date": "Date",
|
||||||
|
"summary": "Changeset {{id}} committed {{time}}"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"name": "Author",
|
||||||
|
"mail": "Mail"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"branch-selector": {
|
||||||
|
"label": "Branches"
|
||||||
|
},
|
||||||
"permission": {
|
"permission": {
|
||||||
"error-title": "Error",
|
"error-title": "Error",
|
||||||
"error-subtitle": "Unknown permissions error",
|
"error-subtitle": "Unknown permissions error",
|
||||||
"name": "User or Group",
|
"name": "User or Group",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
"group-permission": "Group Permission",
|
"group-permission": "Group Permission",
|
||||||
"edit-permission": {
|
"edit-permission": {
|
||||||
"delete-button": "Delete",
|
"delete-button": "Delete",
|
||||||
"save-button": "Save Changes"
|
"save-button": "Save Changes"
|
||||||
},
|
},
|
||||||
"delete-permission-button": {
|
"delete-permission-button": {
|
||||||
"label": "Delete",
|
"label": "Delete",
|
||||||
"confirm-alert": {
|
"confirm-alert": {
|
||||||
"title": "Delete permission",
|
"title": "Delete permission",
|
||||||
"message": "Do you really want to delete the permission?",
|
"message": "Do you really want to delete the permission?",
|
||||||
"submit": "Yes",
|
"submit": "Yes",
|
||||||
"cancel": "No"
|
"cancel": "No"
|
||||||
}
|
|
||||||
},
|
|
||||||
"add-permission": {
|
|
||||||
"add-permission-heading": "Add new Permission",
|
|
||||||
"submit-button": "Submit",
|
|
||||||
"name-input-invalid": "Permission is not allowed to be empty! If it is not empty, your input name is invalid or it already exists!"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"add-permission": {
|
||||||
|
"add-permission-heading": "Add new Permission",
|
||||||
|
"submit-button": "Submit",
|
||||||
|
"name-input-invalid": "Permission is not allowed to be empty! If it is not empty, your input name is invalid or it already exists!"
|
||||||
|
}
|
||||||
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"nameHelpText": "The name of the repository. This name will be part of the repository url.",
|
"nameHelpText": "The name of the repository. This name will be part of the repository url.",
|
||||||
"typeHelpText": "The type of the repository (e.g. Mercurial, Git or Subversion).",
|
"typeHelpText": "The type of the repository (e.g. Mercurial, Git or Subversion).",
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { Route, Redirect, withRouter } from "react-router";
|
import { Redirect, Route, Switch, withRouter } from "react-router-dom";
|
||||||
|
|
||||||
import Overview from "../repos/containers/Overview";
|
import Overview from "../repos/containers/Overview";
|
||||||
import Users from "../users/containers/Users";
|
import Users from "../users/containers/Users";
|
||||||
import Login from "../containers/Login";
|
import Login from "../containers/Login";
|
||||||
import Logout from "../containers/Logout";
|
import Logout from "../containers/Logout";
|
||||||
|
|
||||||
import { Switch } from "react-router-dom";
|
|
||||||
import { ProtectedRoute } from "@scm-manager/ui-components";
|
import { ProtectedRoute } from "@scm-manager/ui-components";
|
||||||
import AddUser from "../users/containers/AddUser";
|
import AddUser from "../users/containers/AddUser";
|
||||||
import SingleUser from "../users/containers/SingleUser";
|
import SingleUser from "../users/containers/SingleUser";
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { routerReducer, routerMiddleware } from "react-router-redux";
|
|||||||
import users from "./users/modules/users";
|
import users from "./users/modules/users";
|
||||||
import repos from "./repos/modules/repos";
|
import repos from "./repos/modules/repos";
|
||||||
import repositoryTypes from "./repos/modules/repositoryTypes";
|
import repositoryTypes from "./repos/modules/repositoryTypes";
|
||||||
|
import changesets from "./repos/modules/changesets";
|
||||||
import groups from "./groups/modules/groups";
|
import groups from "./groups/modules/groups";
|
||||||
import auth from "./modules/auth";
|
import auth from "./modules/auth";
|
||||||
import pending from "./modules/pending";
|
import pending from "./modules/pending";
|
||||||
@@ -15,6 +16,7 @@ import permissions from "./repos/permissions/modules/permissions";
|
|||||||
import config from "./config/modules/config";
|
import config from "./config/modules/config";
|
||||||
|
|
||||||
import type { BrowserHistory } from "history/createBrowserHistory";
|
import type { BrowserHistory } from "history/createBrowserHistory";
|
||||||
|
import branches from "./repos/modules/branches";
|
||||||
|
|
||||||
function createReduxStore(history: BrowserHistory) {
|
function createReduxStore(history: BrowserHistory) {
|
||||||
const composeEnhancers =
|
const composeEnhancers =
|
||||||
@@ -27,6 +29,8 @@ function createReduxStore(history: BrowserHistory) {
|
|||||||
users,
|
users,
|
||||||
repos,
|
repos,
|
||||||
repositoryTypes,
|
repositoryTypes,
|
||||||
|
changesets,
|
||||||
|
branches,
|
||||||
permissions,
|
permissions,
|
||||||
groups,
|
groups,
|
||||||
auth,
|
auth,
|
||||||
|
|||||||
40
scm-ui/src/repos/components/DropDown.js
Normal file
40
scm-ui/src/repos/components/DropDown.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
options: string[],
|
||||||
|
optionSelected: string => void,
|
||||||
|
preselectedOption?: string,
|
||||||
|
className: any
|
||||||
|
};
|
||||||
|
|
||||||
|
class DropDown extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
const { options, preselectedOption, className } = this.props;
|
||||||
|
return (
|
||||||
|
<div className={classNames(className, "select")}>
|
||||||
|
<select
|
||||||
|
value={preselectedOption ? preselectedOption : ""}
|
||||||
|
onChange={this.change}
|
||||||
|
>
|
||||||
|
<option key="" />
|
||||||
|
{options.map(option => {
|
||||||
|
return (
|
||||||
|
<option key={option} value={option}>
|
||||||
|
{option}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
change = (event: SyntheticInputEvent<HTMLSelectElement>) => {
|
||||||
|
this.props.optionSelected(event.target.value);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DropDown;
|
||||||
@@ -4,7 +4,6 @@ import "../../tests/enzyme";
|
|||||||
import "../../tests/i18n";
|
import "../../tests/i18n";
|
||||||
import ReactRouterEnzymeContext from "react-router-enzyme-context";
|
import ReactRouterEnzymeContext from "react-router-enzyme-context";
|
||||||
import PermissionsNavLink from "./PermissionsNavLink";
|
import PermissionsNavLink from "./PermissionsNavLink";
|
||||||
import EditNavLink from "./EditNavLink";
|
|
||||||
|
|
||||||
describe("PermissionsNavLink", () => {
|
describe("PermissionsNavLink", () => {
|
||||||
const options = new ReactRouterEnzymeContext();
|
const options = new ReactRouterEnzymeContext();
|
||||||
|
|||||||
37
scm-ui/src/repos/components/changesets/ChangesetAuthor.js
Normal file
37
scm-ui/src/repos/components/changesets/ChangesetAuthor.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
//@flow
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import type { Changeset } from "@scm-manager/ui-types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
changeset: Changeset
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class ChangesetAuthor extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
const { changeset } = this.props;
|
||||||
|
if (!changeset.author) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name } = changeset.author;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{name} {this.renderMail()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMail() {
|
||||||
|
const { mail } = this.props.changeset.author;
|
||||||
|
if (mail) {
|
||||||
|
return (
|
||||||
|
<a className="is-hidden-mobile" href={"mailto:" + mail}>
|
||||||
|
<
|
||||||
|
{mail}
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
scm-ui/src/repos/components/changesets/ChangesetAvatar.js
Normal file
30
scm-ui/src/repos/components/changesets/ChangesetAvatar.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
//@flow
|
||||||
|
import React from "react";
|
||||||
|
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||||
|
import type { Changeset } from "@scm-manager/ui-types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
changeset: Changeset
|
||||||
|
};
|
||||||
|
|
||||||
|
class ChangesetAvatar extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
const { changeset } = this.props;
|
||||||
|
return (
|
||||||
|
<ExtensionPoint
|
||||||
|
name="repos.changeset-table.information"
|
||||||
|
renderAll={true}
|
||||||
|
props={{ changeset }}
|
||||||
|
>
|
||||||
|
{/* extension should render something like this: */}
|
||||||
|
{/* <div className="image is-64x64"> */}
|
||||||
|
{/* <figure className="media-left"> */}
|
||||||
|
{/* <Image src="/some/image.jpg" alt="Logo" /> */}
|
||||||
|
{/* </figure> */}
|
||||||
|
{/* </div> */}
|
||||||
|
</ExtensionPoint>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChangesetAvatar;
|
||||||
25
scm-ui/src/repos/components/changesets/ChangesetId.js
Normal file
25
scm-ui/src/repos/components/changesets/ChangesetId.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
//@flow
|
||||||
|
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import React from "react";
|
||||||
|
import type { Repository, Changeset } from "@scm-manager/ui-types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
repository: Repository,
|
||||||
|
changeset: Changeset
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class ChangesetId extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
const { repository, changeset } = this.props;
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={`/repo/${repository.namespace}/${repository.name}/changeset/${
|
||||||
|
changeset.id
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{changeset.id.substr(0, 7)}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
scm-ui/src/repos/components/changesets/ChangesetList.js
Normal file
28
scm-ui/src/repos/components/changesets/ChangesetList.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// @flow
|
||||||
|
import ChangesetRow from "./ChangesetRow";
|
||||||
|
import React from "react";
|
||||||
|
import type { Changeset, Repository } from "@scm-manager/ui-types";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
repository: Repository,
|
||||||
|
changesets: Changeset[]
|
||||||
|
};
|
||||||
|
|
||||||
|
class ChangesetList extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
const { repository, changesets } = this.props;
|
||||||
|
const content = changesets.map(changeset => {
|
||||||
|
return (
|
||||||
|
<ChangesetRow
|
||||||
|
key={changeset.id}
|
||||||
|
repository={repository}
|
||||||
|
changeset={changeset}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return <div className={classNames("box")}>{content}</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChangesetList;
|
||||||
90
scm-ui/src/repos/components/changesets/ChangesetRow.js
Normal file
90
scm-ui/src/repos/components/changesets/ChangesetRow.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
//@flow
|
||||||
|
import React from "react";
|
||||||
|
import type { Changeset, Repository, Tag } from "@scm-manager/ui-types";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { translate, Interpolate } from "react-i18next";
|
||||||
|
import ChangesetAvatar from "./ChangesetAvatar";
|
||||||
|
import ChangesetId from "./ChangesetId";
|
||||||
|
import injectSheet from "react-jss";
|
||||||
|
import { DateFromNow } from "@scm-manager/ui-components";
|
||||||
|
import ChangesetAuthor from "./ChangesetAuthor";
|
||||||
|
import ChangesetTag from "./ChangesetTag";
|
||||||
|
import { compose } from "redux";
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
pointer: {
|
||||||
|
cursor: "pointer"
|
||||||
|
},
|
||||||
|
changesetGroup: {
|
||||||
|
marginBottom: "1em"
|
||||||
|
},
|
||||||
|
withOverflow: {
|
||||||
|
overflow: "auto"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
repository: Repository,
|
||||||
|
changeset: Changeset,
|
||||||
|
t: any,
|
||||||
|
classes: any
|
||||||
|
};
|
||||||
|
|
||||||
|
class ChangesetRow extends React.Component<Props> {
|
||||||
|
createLink = (changeset: Changeset) => {
|
||||||
|
const { repository } = this.props;
|
||||||
|
return <ChangesetId changeset={changeset} repository={repository} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
getTags = () => {
|
||||||
|
const { changeset } = this.props;
|
||||||
|
return changeset._embedded.tags || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { changeset, classes } = this.props;
|
||||||
|
const changesetLink = this.createLink(changeset);
|
||||||
|
const dateFromNow = <DateFromNow date={changeset.date} />;
|
||||||
|
const authorLine = <ChangesetAuthor changeset={changeset} />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article className={classNames("media", classes.inner)}>
|
||||||
|
<ChangesetAvatar changeset={changeset} />
|
||||||
|
<div className={classNames("media-content", classes.withOverflow)}>
|
||||||
|
<div className="content">
|
||||||
|
<p className="is-ellipsis-overflow">
|
||||||
|
{changeset.description}
|
||||||
|
<br />
|
||||||
|
<Interpolate
|
||||||
|
i18nKey="changesets.changeset.summary"
|
||||||
|
id={changesetLink}
|
||||||
|
time={dateFromNow}
|
||||||
|
/>
|
||||||
|
</p>{" "}
|
||||||
|
<div className="is-size-7">{authorLine}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{this.renderTags()}
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTags = () => {
|
||||||
|
const tags = this.getTags();
|
||||||
|
if (tags.length > 0) {
|
||||||
|
return (
|
||||||
|
<div className="media-right">
|
||||||
|
{tags.map((tag: Tag) => {
|
||||||
|
return <ChangesetTag key={tag.name} tag={tag} />;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
injectSheet(styles),
|
||||||
|
translate("repos")
|
||||||
|
)(ChangesetRow);
|
||||||
32
scm-ui/src/repos/components/changesets/ChangesetTag.js
Normal file
32
scm-ui/src/repos/components/changesets/ChangesetTag.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//@flow
|
||||||
|
import React from "react";
|
||||||
|
import type { Tag } from "@scm-manager/ui-types";
|
||||||
|
import injectSheet from "react-jss";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
spacing: {
|
||||||
|
marginRight: "4px"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
tag: Tag,
|
||||||
|
|
||||||
|
// context props
|
||||||
|
classes: Object
|
||||||
|
};
|
||||||
|
|
||||||
|
class ChangesetTag extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
const { tag, classes } = this.props;
|
||||||
|
return (
|
||||||
|
<span className="tag is-info">
|
||||||
|
<span className={classNames("fa", "fa-tag", classes.spacing)} />{" "}
|
||||||
|
{tag.name}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default injectSheet(styles)(ChangesetTag);
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Link } from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import injectSheet from "react-jss";
|
import injectSheet from "react-jss";
|
||||||
import type { Repository } from "@scm-manager/ui-types";
|
import type {Repository} from "@scm-manager/ui-types";
|
||||||
import { DateFromNow } from "@scm-manager/ui-components";
|
import {DateFromNow} from "@scm-manager/ui-components";
|
||||||
import RepositoryEntryLink from "./RepositoryEntryLink";
|
import RepositoryEntryLink from "./RepositoryEntryLink";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import RepositoryAvatar from "./RepositoryAvatar";
|
import RepositoryAvatar from "./RepositoryAvatar";
|
||||||
@@ -45,7 +45,7 @@ class RepositoryEntry extends React.Component<Props> {
|
|||||||
return (
|
return (
|
||||||
<RepositoryEntryLink
|
<RepositoryEntryLink
|
||||||
iconClass="fa-code-branch"
|
iconClass="fa-code-branch"
|
||||||
to={repositoryLink + "/changesets"}
|
to={repositoryLink + "/history"}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -67,10 +67,7 @@ class RepositoryEntry extends React.Component<Props> {
|
|||||||
renderModifyLink = (repository: Repository, repositoryLink: string) => {
|
renderModifyLink = (repository: Repository, repositoryLink: string) => {
|
||||||
if (repository._links["update"]) {
|
if (repository._links["update"]) {
|
||||||
return (
|
return (
|
||||||
<RepositoryEntryLink
|
<RepositoryEntryLink iconClass="fa-cog" to={repositoryLink + "/edit"} />
|
||||||
iconClass="fa-cog"
|
|
||||||
to={repositoryLink + "/modify"}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
139
scm-ui/src/repos/containers/BranchRoot.js
Normal file
139
scm-ui/src/repos/containers/BranchRoot.js
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import type { Branch, Repository } from "@scm-manager/ui-types";
|
||||||
|
import { Route, withRouter } from "react-router-dom";
|
||||||
|
import Changesets from "./Changesets";
|
||||||
|
import BranchSelector from "./BranchSelector";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { ErrorNotification, Loading } from "@scm-manager/ui-components";
|
||||||
|
import {
|
||||||
|
fetchBranches,
|
||||||
|
getBranches,
|
||||||
|
getFetchBranchesFailure,
|
||||||
|
isFetchBranchesPending
|
||||||
|
} from "../modules/branches";
|
||||||
|
import { compose } from "redux";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
repository: Repository,
|
||||||
|
baseUrl: string,
|
||||||
|
selected: string,
|
||||||
|
baseUrlWithBranch: string,
|
||||||
|
baseUrlWithoutBranch: string,
|
||||||
|
|
||||||
|
// State props
|
||||||
|
branches: Branch[],
|
||||||
|
loading: boolean,
|
||||||
|
error: Error,
|
||||||
|
|
||||||
|
// Dispatch props
|
||||||
|
fetchBranches: Repository => void,
|
||||||
|
|
||||||
|
// Context props
|
||||||
|
history: any, // TODO flow type
|
||||||
|
match: any
|
||||||
|
};
|
||||||
|
|
||||||
|
class BranchRoot extends React.Component<Props> {
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.fetchBranches(this.props.repository);
|
||||||
|
}
|
||||||
|
|
||||||
|
stripEndingSlash = (url: string) => {
|
||||||
|
if (url.endsWith("/")) {
|
||||||
|
return url.substring(0, url.length - 1);
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
branchSelected = (branch?: Branch) => {
|
||||||
|
let url;
|
||||||
|
if (branch) {
|
||||||
|
url = `${this.props.baseUrlWithBranch}/${encodeURIComponent(
|
||||||
|
branch.name
|
||||||
|
)}/changesets/`;
|
||||||
|
} else {
|
||||||
|
url = `${this.props.baseUrlWithoutBranch}/`;
|
||||||
|
}
|
||||||
|
this.props.history.push(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
findSelectedBranch = () => {
|
||||||
|
const { selected, branches } = this.props;
|
||||||
|
return branches.find((branch: Branch) => branch.name === selected);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { repository, error, loading, match, branches } = this.props;
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <ErrorNotification error={error} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!repository || !branches) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = this.stripEndingSlash(match.url);
|
||||||
|
const branch = this.findSelectedBranch();
|
||||||
|
const changesets = <Changesets repository={repository} branch={branch} />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{this.renderBranchSelector()}
|
||||||
|
<Route path={`${url}/:page?`} component={() => changesets} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderBranchSelector = () => {
|
||||||
|
const { repository, branches } = this.props;
|
||||||
|
if (repository._links.branches) {
|
||||||
|
return (
|
||||||
|
<BranchSelector
|
||||||
|
branches={branches}
|
||||||
|
selected={(b: Branch) => {
|
||||||
|
this.branchSelected(b);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
fetchBranches: (repo: Repository) => {
|
||||||
|
dispatch(fetchBranches(repo));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (state: any, ownProps: Props) => {
|
||||||
|
const { repository, match } = ownProps;
|
||||||
|
const loading = isFetchBranchesPending(state, repository);
|
||||||
|
const error = getFetchBranchesFailure(state, repository);
|
||||||
|
const branches = getBranches(state, repository);
|
||||||
|
const selected = decodeURIComponent(match.params.branch);
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
branches,
|
||||||
|
selected
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withRouter,
|
||||||
|
connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)
|
||||||
|
)(BranchRoot);
|
||||||
78
scm-ui/src/repos/containers/BranchSelector.js
Normal file
78
scm-ui/src/repos/containers/BranchSelector.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import type { Branch } from "@scm-manager/ui-types";
|
||||||
|
import DropDown from "../components/DropDown";
|
||||||
|
import { translate } from "react-i18next";
|
||||||
|
import injectSheet from "react-jss";
|
||||||
|
import { compose } from "redux";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
zeroflex: {
|
||||||
|
flexGrow: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
branches: Branch[], // TODO: Use generics?
|
||||||
|
selected: (branch?: Branch) => void,
|
||||||
|
|
||||||
|
// context props
|
||||||
|
classes: Object,
|
||||||
|
t: string => string
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = { selectedBranch?: Branch };
|
||||||
|
|
||||||
|
class BranchSelector extends React.Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { branches, classes, t } = this.props;
|
||||||
|
|
||||||
|
if (branches) {
|
||||||
|
return (
|
||||||
|
<div className="box field is-horizontal">
|
||||||
|
<div
|
||||||
|
className={classNames("field-label", "is-normal", classes.zeroflex)}
|
||||||
|
>
|
||||||
|
<label className="label">{t("branch-selector.label")}</label>
|
||||||
|
</div>
|
||||||
|
<div className="field-body">
|
||||||
|
<div className="field is-narrow">
|
||||||
|
<div className="control">
|
||||||
|
<DropDown
|
||||||
|
className="is-fullwidth"
|
||||||
|
options={branches.map(b => b.name)}
|
||||||
|
optionSelected={this.branchSelected}
|
||||||
|
preselectedOption={
|
||||||
|
this.state.selectedBranch
|
||||||
|
? this.state.selectedBranch.name
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
branchSelected = (branchName: string) => {
|
||||||
|
const { branches, selected } = this.props;
|
||||||
|
const branch = branches.find(b => b.name === branchName);
|
||||||
|
|
||||||
|
selected(branch);
|
||||||
|
this.setState({ selectedBranch: branch });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
injectSheet(styles),
|
||||||
|
translate("repos")
|
||||||
|
)(BranchSelector);
|
||||||
115
scm-ui/src/repos/containers/Changesets.js
Normal file
115
scm-ui/src/repos/containers/Changesets.js
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { withRouter } from "react-router-dom";
|
||||||
|
import type {
|
||||||
|
Branch,
|
||||||
|
Changeset,
|
||||||
|
PagedCollection,
|
||||||
|
Repository
|
||||||
|
} from "@scm-manager/ui-types";
|
||||||
|
import {
|
||||||
|
fetchChangesets,
|
||||||
|
getChangesets,
|
||||||
|
getFetchChangesetsFailure,
|
||||||
|
isFetchChangesetsPending,
|
||||||
|
selectListAsCollection
|
||||||
|
} from "../modules/changesets";
|
||||||
|
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import ChangesetList from "../components/changesets/ChangesetList";
|
||||||
|
import {
|
||||||
|
ErrorNotification,
|
||||||
|
LinkPaginator,
|
||||||
|
Loading,
|
||||||
|
getPageFromMatch
|
||||||
|
} from "@scm-manager/ui-components";
|
||||||
|
import { compose } from "redux";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
repository: Repository,
|
||||||
|
branch: Branch,
|
||||||
|
page: number,
|
||||||
|
|
||||||
|
// State props
|
||||||
|
changesets: Changeset[],
|
||||||
|
list: PagedCollection,
|
||||||
|
loading: boolean,
|
||||||
|
error: Error,
|
||||||
|
|
||||||
|
// Dispatch props
|
||||||
|
fetchChangesets: (Repository, Branch, number) => void,
|
||||||
|
|
||||||
|
// context props
|
||||||
|
match: any
|
||||||
|
};
|
||||||
|
|
||||||
|
class Changesets extends React.Component<Props> {
|
||||||
|
componentDidMount() {
|
||||||
|
const { fetchChangesets, repository, branch, page } = this.props;
|
||||||
|
|
||||||
|
fetchChangesets(repository, branch, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { changesets, loading, error } = this.props;
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <ErrorNotification error={error} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!changesets || changesets.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{this.renderList()}
|
||||||
|
{this.renderPaginator()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderList = () => {
|
||||||
|
const { repository, changesets } = this.props;
|
||||||
|
return <ChangesetList repository={repository} changesets={changesets} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
renderPaginator = () => {
|
||||||
|
const { page, list } = this.props;
|
||||||
|
if (list) {
|
||||||
|
return <LinkPaginator page={page} collection={list} />;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
fetchChangesets: (repo: Repository, branch: Branch, page: number) => {
|
||||||
|
dispatch(fetchChangesets(repo, branch, page));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (state: any, ownProps: Props) => {
|
||||||
|
const { repository, branch, match } = ownProps;
|
||||||
|
const changesets = getChangesets(state, repository, branch);
|
||||||
|
const loading = isFetchChangesetsPending(state, repository, branch);
|
||||||
|
const error = getFetchChangesetsFailure(state, repository, branch);
|
||||||
|
const list = selectListAsCollection(state, repository, branch);
|
||||||
|
const page = getPageFromMatch(match);
|
||||||
|
|
||||||
|
return { changesets, list, page, loading, error };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withRouter,
|
||||||
|
connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)
|
||||||
|
)(Changesets);
|
||||||
@@ -8,14 +8,14 @@ import {
|
|||||||
isFetchRepoPending
|
isFetchRepoPending
|
||||||
} from "../modules/repos";
|
} from "../modules/repos";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Route } from "react-router-dom";
|
import { Route, Switch } from "react-router-dom";
|
||||||
import type { Repository } from "@scm-manager/ui-types";
|
import type { Repository } from "@scm-manager/ui-types";
|
||||||
import {
|
import {
|
||||||
Page,
|
|
||||||
Loading,
|
|
||||||
ErrorPage,
|
ErrorPage,
|
||||||
|
Loading,
|
||||||
Navigation,
|
Navigation,
|
||||||
NavLink,
|
NavLink,
|
||||||
|
Page,
|
||||||
Section
|
Section
|
||||||
} from "@scm-manager/ui-components";
|
} from "@scm-manager/ui-components";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
@@ -26,6 +26,7 @@ import Permissions from "../permissions/containers/Permissions";
|
|||||||
|
|
||||||
import type { History } from "history";
|
import type { History } from "history";
|
||||||
import EditNavLink from "../components/EditNavLink";
|
import EditNavLink from "../components/EditNavLink";
|
||||||
|
import BranchRoot from "./BranchRoot";
|
||||||
import PermissionsNavLink from "../components/PermissionsNavLink";
|
import PermissionsNavLink from "../components/PermissionsNavLink";
|
||||||
import ScmDiff from "./ScmDiff";
|
import ScmDiff from "./ScmDiff";
|
||||||
|
|
||||||
@@ -72,6 +73,12 @@ class RepositoryRoot extends React.Component<Props> {
|
|||||||
this.props.deleteRepo(repository, this.deleted);
|
this.props.deleteRepo(repository, this.deleted);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
matches = (route: any) => {
|
||||||
|
const url = this.matchedUrl();
|
||||||
|
const regex = new RegExp(`${url}(/branches)?/?[^/]*/changesets?.*`);
|
||||||
|
return route.location.pathname.match(regex);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { loading, error, repository, t } = this.props;
|
const { loading, error, repository, t } = this.props;
|
||||||
|
|
||||||
@@ -90,49 +97,76 @@ class RepositoryRoot extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const url = this.matchedUrl();
|
const url = this.matchedUrl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page title={repository.namespace + "/" + repository.name}>
|
<Page title={repository.namespace + "/" + repository.name}>
|
||||||
<div className="columns">
|
<div className="columns">
|
||||||
<div className="column is-three-quarters">
|
<div className="column is-three-quarters">
|
||||||
<Route
|
<Switch>
|
||||||
path={url}
|
<Route
|
||||||
exact
|
path={url}
|
||||||
component={() => <RepositoryDetails repository={repository} />}
|
exact
|
||||||
/>
|
component={() => <RepositoryDetails repository={repository} />}
|
||||||
<Route
|
/>
|
||||||
path={`${url}/edit`}
|
<Route
|
||||||
component={() => <Edit repository={repository} />}
|
path={`${url}/edit`}
|
||||||
/>
|
component={() => <Edit repository={repository} />}
|
||||||
<Route
|
/>
|
||||||
path={`${url}/permissions`}
|
<Route
|
||||||
render={props => (
|
path={`${url}/permissions`}
|
||||||
<Permissions
|
render={props => (
|
||||||
namespace={this.props.repository.namespace}
|
<Permissions
|
||||||
repoName={this.props.repository.name}
|
namespace={this.props.repository.namespace}
|
||||||
/>
|
repoName={this.props.repository.name}
|
||||||
)}
|
/>
|
||||||
/>
|
)}
|
||||||
<Route
|
/>
|
||||||
path={`${url}/diff`}
|
<Route
|
||||||
component={() => (
|
path={`${url}/changesets`}
|
||||||
<ScmDiff
|
render={() => (
|
||||||
namespace={repository.namespace}
|
<BranchRoot
|
||||||
name={repository.name}
|
repository={repository}
|
||||||
revision={"db64ca5992ad8e327c7687d49f5297bef7b29893"}
|
baseUrlWithBranch={`${url}/branches`}
|
||||||
/>
|
baseUrlWithoutBranch={`${url}/changesets`}
|
||||||
)}
|
/>
|
||||||
/>
|
)}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={`${url}/branches/:branch/changesets`}
|
||||||
|
render={() => (
|
||||||
|
<BranchRoot
|
||||||
|
repository={repository}
|
||||||
|
baseUrlWithBranch={`${url}/branches`}
|
||||||
|
baseUrlWithoutBranch={`${url}/changesets`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={`${url}/diff`}
|
||||||
|
component={() => (
|
||||||
|
<ScmDiff
|
||||||
|
namespace={repository.namespace}
|
||||||
|
name={repository.name}
|
||||||
|
revision={"db64ca5992ad8e327c7687d49f5297bef7b29893"}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
<div className="column">
|
<div className="column">
|
||||||
<Navigation>
|
<Navigation>
|
||||||
<Section label={t("repository-root.navigation-label")}>
|
<Section label={t("repository-root.navigation-label")}>
|
||||||
<NavLink to={url} label={t("repository-root.information")} />
|
<NavLink to={url} label={t("repository-root.information")} />
|
||||||
|
<NavLink
|
||||||
|
activeOnlyWhenExact={false}
|
||||||
|
to={`${url}/changesets/`}
|
||||||
|
label={t("repository-root.history")}
|
||||||
|
activeWhenMatch={this.matches}
|
||||||
|
/>
|
||||||
|
<EditNavLink repository={repository} editUrl={`${url}/edit`} />
|
||||||
<PermissionsNavLink
|
<PermissionsNavLink
|
||||||
permissionUrl={`${url}/permissions`}
|
permissionUrl={`${url}/permissions`}
|
||||||
repository={repository}
|
repository={repository}
|
||||||
/>
|
/>
|
||||||
<EditNavLink repository={repository} editUrl={`${url}/edit`} />
|
|
||||||
</Section>
|
</Section>
|
||||||
<Section label={t("repository-root.actions-label")}>
|
<Section label={t("repository-root.actions-label")}>
|
||||||
<DeleteNavAction repository={repository} delete={this.delete} />
|
<DeleteNavAction repository={repository} delete={this.delete} />
|
||||||
|
|||||||
134
scm-ui/src/repos/modules/branches.js
Normal file
134
scm-ui/src/repos/modules/branches.js
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
// @flow
|
||||||
|
import {
|
||||||
|
FAILURE_SUFFIX,
|
||||||
|
PENDING_SUFFIX,
|
||||||
|
SUCCESS_SUFFIX
|
||||||
|
} from "../../modules/types";
|
||||||
|
import { apiClient } from "@scm-manager/ui-components";
|
||||||
|
import type { Action, Branch, Repository } from "@scm-manager/ui-types";
|
||||||
|
import { isPending } from "../../modules/pending";
|
||||||
|
import { getFailure } from "../../modules/failure";
|
||||||
|
|
||||||
|
export const FETCH_BRANCHES = "scm/repos/FETCH_BRANCHES";
|
||||||
|
export const FETCH_BRANCHES_PENDING = `${FETCH_BRANCHES}_${PENDING_SUFFIX}`;
|
||||||
|
export const FETCH_BRANCHES_SUCCESS = `${FETCH_BRANCHES}_${SUCCESS_SUFFIX}`;
|
||||||
|
export const FETCH_BRANCHES_FAILURE = `${FETCH_BRANCHES}_${FAILURE_SUFFIX}`;
|
||||||
|
|
||||||
|
// Fetching branches
|
||||||
|
|
||||||
|
export function fetchBranches(repository: Repository) {
|
||||||
|
if (!repository._links.branches) {
|
||||||
|
return {
|
||||||
|
type: FETCH_BRANCHES_SUCCESS,
|
||||||
|
payload: { repository, data: {} },
|
||||||
|
itemId: createKey(repository)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return function(dispatch: any) {
|
||||||
|
dispatch(fetchBranchesPending(repository));
|
||||||
|
return apiClient
|
||||||
|
.get(repository._links.branches.href)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
dispatch(fetchBranchesSuccess(data, repository));
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
dispatch(fetchBranchesFailure(repository, error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action creators
|
||||||
|
export function fetchBranchesPending(repository: Repository) {
|
||||||
|
return {
|
||||||
|
type: FETCH_BRANCHES_PENDING,
|
||||||
|
payload: { repository },
|
||||||
|
itemId: createKey(repository)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchBranchesSuccess(data: string, repository: Repository) {
|
||||||
|
return {
|
||||||
|
type: FETCH_BRANCHES_SUCCESS,
|
||||||
|
payload: { data, repository },
|
||||||
|
itemId: createKey(repository)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchBranchesFailure(repository: Repository, error: Error) {
|
||||||
|
return {
|
||||||
|
type: FETCH_BRANCHES_FAILURE,
|
||||||
|
payload: { error, repository },
|
||||||
|
itemId: createKey(repository)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reducers
|
||||||
|
|
||||||
|
type State = { [string]: Branch[] };
|
||||||
|
|
||||||
|
export default function reducer(
|
||||||
|
state: State = {},
|
||||||
|
action: Action = { type: "UNKNOWN" }
|
||||||
|
): State {
|
||||||
|
if (!action.payload) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
const payload = action.payload;
|
||||||
|
switch (action.type) {
|
||||||
|
case FETCH_BRANCHES_SUCCESS:
|
||||||
|
const key = createKey(payload.repository);
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
[key]: extractBranchesFromPayload(payload.data)
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractBranchesFromPayload(payload: any) {
|
||||||
|
if (payload._embedded && payload._embedded.branches) {
|
||||||
|
return payload._embedded.branches;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selectors
|
||||||
|
|
||||||
|
export function getBranches(state: Object, repository: Repository) {
|
||||||
|
const key = createKey(repository);
|
||||||
|
if (state.branches[key]) {
|
||||||
|
return state.branches[key];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBranch(
|
||||||
|
state: Object,
|
||||||
|
repository: Repository,
|
||||||
|
name: string
|
||||||
|
): ?Branch {
|
||||||
|
const key = createKey(repository);
|
||||||
|
if (state.branches[key]) {
|
||||||
|
return state.branches[key].find((b: Branch) => b.name === name);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFetchBranchesPending(
|
||||||
|
state: Object,
|
||||||
|
repository: Repository
|
||||||
|
): boolean {
|
||||||
|
return isPending(state, FETCH_BRANCHES, createKey(repository));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFetchBranchesFailure(state: Object, repository: Repository) {
|
||||||
|
return getFailure(state, FETCH_BRANCHES, createKey(repository));
|
||||||
|
}
|
||||||
|
|
||||||
|
function createKey(repository: Repository): string {
|
||||||
|
const { namespace, name } = repository;
|
||||||
|
return `${namespace}/${name}`;
|
||||||
|
}
|
||||||
195
scm-ui/src/repos/modules/branches.test.js
Normal file
195
scm-ui/src/repos/modules/branches.test.js
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
import configureMockStore from "redux-mock-store";
|
||||||
|
import thunk from "redux-thunk";
|
||||||
|
import fetchMock from "fetch-mock";
|
||||||
|
import reducer, {
|
||||||
|
FETCH_BRANCHES,
|
||||||
|
FETCH_BRANCHES_FAILURE,
|
||||||
|
FETCH_BRANCHES_PENDING,
|
||||||
|
FETCH_BRANCHES_SUCCESS,
|
||||||
|
fetchBranches,
|
||||||
|
getBranch,
|
||||||
|
getBranches,
|
||||||
|
getFetchBranchesFailure,
|
||||||
|
isFetchBranchesPending
|
||||||
|
} from "./branches";
|
||||||
|
|
||||||
|
const namespace = "foo";
|
||||||
|
const name = "bar";
|
||||||
|
const key = namespace + "/" + name;
|
||||||
|
const repository = {
|
||||||
|
namespace: "foo",
|
||||||
|
name: "bar",
|
||||||
|
_links: {
|
||||||
|
branches: {
|
||||||
|
href: "http://scm/api/rest/v2/repositories/foo/bar/branches"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const branch1 = { name: "branch1", revision: "revision1" };
|
||||||
|
const branch2 = { name: "branch2", revision: "revision2" };
|
||||||
|
const branch3 = { name: "branch3", revision: "revision3" };
|
||||||
|
|
||||||
|
describe("branches", () => {
|
||||||
|
describe("fetch branches", () => {
|
||||||
|
const URL = "http://scm/api/rest/v2/repositories/foo/bar/branches";
|
||||||
|
const mockStore = configureMockStore([thunk]);
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fetchMock.reset();
|
||||||
|
fetchMock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fetch branches", () => {
|
||||||
|
const collection = {};
|
||||||
|
|
||||||
|
fetchMock.getOnce(URL, "{}");
|
||||||
|
|
||||||
|
const expectedActions = [
|
||||||
|
{
|
||||||
|
type: FETCH_BRANCHES_PENDING,
|
||||||
|
payload: { repository },
|
||||||
|
itemId: key
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: FETCH_BRANCHES_SUCCESS,
|
||||||
|
payload: { data: collection, repository },
|
||||||
|
itemId: key
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
return store.dispatch(fetchBranches(repository)).then(() => {
|
||||||
|
expect(store.getActions()).toEqual(expectedActions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail fetching branches on HTTP 500", () => {
|
||||||
|
const collection = {};
|
||||||
|
|
||||||
|
fetchMock.getOnce(URL, 500);
|
||||||
|
|
||||||
|
const expectedActions = [
|
||||||
|
{
|
||||||
|
type: FETCH_BRANCHES_PENDING,
|
||||||
|
payload: { repository },
|
||||||
|
itemId: key
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: FETCH_BRANCHES_FAILURE,
|
||||||
|
payload: { error: collection, repository },
|
||||||
|
itemId: key
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
return store.dispatch(fetchBranches(repository)).then(() => {
|
||||||
|
expect(store.getActions()[0]).toEqual(expectedActions[0]);
|
||||||
|
expect(store.getActions()[1].type).toEqual(FETCH_BRANCHES_FAILURE);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("branches reducer", () => {
|
||||||
|
const branches = {
|
||||||
|
_embedded: {
|
||||||
|
branches: [branch1, branch2]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const action = {
|
||||||
|
type: FETCH_BRANCHES_SUCCESS,
|
||||||
|
payload: {
|
||||||
|
repository,
|
||||||
|
data: branches
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
it("should update state according to successful fetch", () => {
|
||||||
|
const newState = reducer({}, action);
|
||||||
|
expect(newState).toBeDefined();
|
||||||
|
expect(newState[key]).toBeDefined();
|
||||||
|
expect(newState[key]).toContain(branch1);
|
||||||
|
expect(newState[key]).toContain(branch2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not delete existing branches from state", () => {
|
||||||
|
const oldState = {
|
||||||
|
"hitchhiker/heartOfGold": [branch3]
|
||||||
|
};
|
||||||
|
|
||||||
|
const newState = reducer(oldState, action);
|
||||||
|
expect(newState[key]).toContain(branch1);
|
||||||
|
expect(newState[key]).toContain(branch2);
|
||||||
|
|
||||||
|
expect(newState["hitchhiker/heartOfGold"]).toContain(branch3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("branch selectors", () => {
|
||||||
|
const error = new Error("Something went wrong");
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
branches: {
|
||||||
|
[key]: [branch1, branch2]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
it("should return true, when fetching branches is pending", () => {
|
||||||
|
const state = {
|
||||||
|
pending: {
|
||||||
|
[FETCH_BRANCHES + "/foo/bar"]: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(isFetchBranchesPending(state, repository)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return branches", () => {
|
||||||
|
const branches = getBranches(state, repository);
|
||||||
|
expect(branches.length).toEqual(2);
|
||||||
|
expect(branches).toContain(branch1);
|
||||||
|
expect(branches).toContain(branch2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return always the same reference for branches", () => {
|
||||||
|
const one = getBranches(state, repository);
|
||||||
|
const two = getBranches(state, repository);
|
||||||
|
expect(one).toBe(two);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return null, if no branches for the repository available", () => {
|
||||||
|
const branches = getBranches({ branches: {} }, repository);
|
||||||
|
expect(branches).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return single branch by name", () => {
|
||||||
|
const branch = getBranch(state, repository, "branch1");
|
||||||
|
expect(branch).toEqual(branch1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return same reference for single branch by name", () => {
|
||||||
|
const one = getBranch(state, repository, "branch1");
|
||||||
|
const two = getBranch(state, repository, "branch1");
|
||||||
|
expect(one).toBe(two);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return undefined if branch does not exist", () => {
|
||||||
|
const branch = getBranch(state, repository, "branch42");
|
||||||
|
expect(branch).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return error if fetching branches failed", () => {
|
||||||
|
const state = {
|
||||||
|
failure: {
|
||||||
|
[FETCH_BRANCHES + "/foo/bar"]: error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getFetchBranchesFailure(state, repository)).toEqual(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false if fetching branches did not fail", () => {
|
||||||
|
expect(getFetchBranchesFailure({}, repository)).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
230
scm-ui/src/repos/modules/changesets.js
Normal file
230
scm-ui/src/repos/modules/changesets.js
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import {
|
||||||
|
FAILURE_SUFFIX,
|
||||||
|
PENDING_SUFFIX,
|
||||||
|
SUCCESS_SUFFIX
|
||||||
|
} from "../../modules/types";
|
||||||
|
import { apiClient } from "@scm-manager/ui-components";
|
||||||
|
import { isPending } from "../../modules/pending";
|
||||||
|
import { getFailure } from "../../modules/failure";
|
||||||
|
import type {
|
||||||
|
Action,
|
||||||
|
Branch,
|
||||||
|
PagedCollection,
|
||||||
|
Repository
|
||||||
|
} from "@scm-manager/ui-types";
|
||||||
|
|
||||||
|
export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS";
|
||||||
|
export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`;
|
||||||
|
export const FETCH_CHANGESETS_SUCCESS = `${FETCH_CHANGESETS}_${SUCCESS_SUFFIX}`;
|
||||||
|
export const FETCH_CHANGESETS_FAILURE = `${FETCH_CHANGESETS}_${FAILURE_SUFFIX}`;
|
||||||
|
|
||||||
|
//TODO: Content type
|
||||||
|
// actions
|
||||||
|
|
||||||
|
export function fetchChangesets(
|
||||||
|
repository: Repository,
|
||||||
|
branch?: Branch,
|
||||||
|
page?: number
|
||||||
|
) {
|
||||||
|
const link = createChangesetsLink(repository, branch, page);
|
||||||
|
|
||||||
|
return function(dispatch: any) {
|
||||||
|
dispatch(fetchChangesetsPending(repository, branch));
|
||||||
|
return apiClient
|
||||||
|
.get(link)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
dispatch(fetchChangesetsSuccess(repository, branch, data));
|
||||||
|
})
|
||||||
|
.catch(cause => {
|
||||||
|
dispatch(fetchChangesetsFailure(repository, branch, cause));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createChangesetsLink(
|
||||||
|
repository: Repository,
|
||||||
|
branch?: Branch,
|
||||||
|
page?: number
|
||||||
|
) {
|
||||||
|
let link = repository._links.changesets.href;
|
||||||
|
|
||||||
|
if (branch) {
|
||||||
|
link = branch._links.history.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page) {
|
||||||
|
link = link + `?page=${page - 1}`;
|
||||||
|
}
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchChangesetsPending(
|
||||||
|
repository: Repository,
|
||||||
|
branch?: Branch
|
||||||
|
): Action {
|
||||||
|
const itemId = createItemId(repository, branch);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: FETCH_CHANGESETS_PENDING,
|
||||||
|
itemId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchChangesetsSuccess(
|
||||||
|
repository: Repository,
|
||||||
|
branch?: Branch,
|
||||||
|
changesets: any
|
||||||
|
): Action {
|
||||||
|
return {
|
||||||
|
type: FETCH_CHANGESETS_SUCCESS,
|
||||||
|
payload: changesets,
|
||||||
|
itemId: createItemId(repository, branch)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchChangesetsFailure(
|
||||||
|
repository: Repository,
|
||||||
|
branch?: Branch,
|
||||||
|
error: Error
|
||||||
|
): Action {
|
||||||
|
return {
|
||||||
|
type: FETCH_CHANGESETS_FAILURE,
|
||||||
|
payload: {
|
||||||
|
repository,
|
||||||
|
error,
|
||||||
|
branch
|
||||||
|
},
|
||||||
|
itemId: createItemId(repository, branch)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createItemId(repository: Repository, branch?: Branch): string {
|
||||||
|
const { namespace, name } = repository;
|
||||||
|
let itemId = namespace + "/" + name;
|
||||||
|
if (branch) {
|
||||||
|
itemId = itemId + "/" + branch.name;
|
||||||
|
}
|
||||||
|
return itemId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reducer
|
||||||
|
export default function reducer(
|
||||||
|
state: any = {},
|
||||||
|
action: Action = { type: "UNKNOWN" }
|
||||||
|
): Object {
|
||||||
|
if (!action.payload) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
const payload = action.payload;
|
||||||
|
switch (action.type) {
|
||||||
|
case FETCH_CHANGESETS_SUCCESS:
|
||||||
|
const changesets = payload._embedded.changesets;
|
||||||
|
const changesetIds = changesets.map(c => c.id);
|
||||||
|
const key = action.itemId;
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
let oldByIds = {};
|
||||||
|
if (state[key] && state[key].byId) {
|
||||||
|
oldByIds = state[key].byId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const byIds = extractChangesetsByIds(changesets);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
[key]: {
|
||||||
|
byId: {
|
||||||
|
...oldByIds,
|
||||||
|
...byIds
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
entries: changesetIds,
|
||||||
|
entry: {
|
||||||
|
page: payload.page,
|
||||||
|
pageTotal: payload.pageTotal,
|
||||||
|
_links: payload._links
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractChangesetsByIds(changesets: any) {
|
||||||
|
const changesetsByIds = {};
|
||||||
|
|
||||||
|
for (let changeset of changesets) {
|
||||||
|
changesetsByIds[changeset.id] = changeset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return changesetsByIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
//selectors
|
||||||
|
export function getChangesets(
|
||||||
|
state: Object,
|
||||||
|
repository: Repository,
|
||||||
|
branch?: Branch
|
||||||
|
) {
|
||||||
|
const key = createItemId(repository, branch);
|
||||||
|
|
||||||
|
const changesets = state.changesets[key];
|
||||||
|
if (!changesets) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return changesets.list.entries.map((id: string) => {
|
||||||
|
return changesets.byId[id];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFetchChangesetsPending(
|
||||||
|
state: Object,
|
||||||
|
repository: Repository,
|
||||||
|
branch?: Branch
|
||||||
|
) {
|
||||||
|
return isPending(state, FETCH_CHANGESETS, createItemId(repository, branch));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFetchChangesetsFailure(
|
||||||
|
state: Object,
|
||||||
|
repository: Repository,
|
||||||
|
branch?: Branch
|
||||||
|
) {
|
||||||
|
return getFailure(state, FETCH_CHANGESETS, createItemId(repository, branch));
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectList = (state: Object, repository: Repository, branch?: Branch) => {
|
||||||
|
const itemId = createItemId(repository, branch);
|
||||||
|
if (state.changesets[itemId] && state.changesets[itemId].list) {
|
||||||
|
return state.changesets[itemId].list;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectListEntry = (
|
||||||
|
state: Object,
|
||||||
|
repository: Repository,
|
||||||
|
branch?: Branch
|
||||||
|
): Object => {
|
||||||
|
const list = selectList(state, repository, branch);
|
||||||
|
if (list.entry) {
|
||||||
|
return list.entry;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const selectListAsCollection = (
|
||||||
|
state: Object,
|
||||||
|
repository: Repository,
|
||||||
|
branch?: Branch
|
||||||
|
): PagedCollection => {
|
||||||
|
return selectListEntry(state, repository, branch);
|
||||||
|
};
|
||||||
307
scm-ui/src/repos/modules/changesets.test.js
Normal file
307
scm-ui/src/repos/modules/changesets.test.js
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import configureMockStore from "redux-mock-store";
|
||||||
|
import thunk from "redux-thunk";
|
||||||
|
import fetchMock from "fetch-mock";
|
||||||
|
import reducer, {
|
||||||
|
FETCH_CHANGESETS,
|
||||||
|
FETCH_CHANGESETS_FAILURE,
|
||||||
|
FETCH_CHANGESETS_PENDING,
|
||||||
|
FETCH_CHANGESETS_SUCCESS,
|
||||||
|
fetchChangesets,
|
||||||
|
fetchChangesetsSuccess,
|
||||||
|
getChangesets,
|
||||||
|
getFetchChangesetsFailure,
|
||||||
|
isFetchChangesetsPending
|
||||||
|
} from "./changesets";
|
||||||
|
|
||||||
|
const branch = {
|
||||||
|
name: "specific",
|
||||||
|
revision: "123",
|
||||||
|
_links: {
|
||||||
|
history: {
|
||||||
|
href:
|
||||||
|
"http://scm/api/rest/v2/repositories/foo/bar/branches/specific/changesets"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const repository = {
|
||||||
|
namespace: "foo",
|
||||||
|
name: "bar",
|
||||||
|
type: "GIT",
|
||||||
|
_links: {
|
||||||
|
self: {
|
||||||
|
href: "http://scm/api/rest/v2/repositories/foo/bar"
|
||||||
|
},
|
||||||
|
changesets: {
|
||||||
|
href: "http://scm/api/rest/v2/repositories/foo/bar/changesets"
|
||||||
|
},
|
||||||
|
branches: {
|
||||||
|
href:
|
||||||
|
"http://scm/api/rest/v2/repositories/foo/bar/branches/specific/branches"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const changesets = {};
|
||||||
|
|
||||||
|
describe("changesets", () => {
|
||||||
|
describe("fetching of changesets", () => {
|
||||||
|
const DEFAULT_BRANCH_URL =
|
||||||
|
"http://scm/api/rest/v2/repositories/foo/bar/changesets";
|
||||||
|
const SPECIFIC_BRANCH_URL =
|
||||||
|
"http://scm/api/rest/v2/repositories/foo/bar/branches/specific/changesets";
|
||||||
|
const mockStore = configureMockStore([thunk]);
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fetchMock.reset();
|
||||||
|
fetchMock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fetch changesets for default branch", () => {
|
||||||
|
fetchMock.getOnce(DEFAULT_BRANCH_URL, "{}");
|
||||||
|
|
||||||
|
const expectedActions = [
|
||||||
|
{
|
||||||
|
type: FETCH_CHANGESETS_PENDING,
|
||||||
|
itemId: "foo/bar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: FETCH_CHANGESETS_SUCCESS,
|
||||||
|
payload: changesets,
|
||||||
|
itemId: "foo/bar"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
return store.dispatch(fetchChangesets(repository)).then(() => {
|
||||||
|
expect(store.getActions()).toEqual(expectedActions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fetch changesets for specific branch", () => {
|
||||||
|
const itemId = "foo/bar/specific";
|
||||||
|
fetchMock.getOnce(SPECIFIC_BRANCH_URL, "{}");
|
||||||
|
|
||||||
|
const expectedActions = [
|
||||||
|
{
|
||||||
|
type: FETCH_CHANGESETS_PENDING,
|
||||||
|
itemId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: FETCH_CHANGESETS_SUCCESS,
|
||||||
|
payload: changesets,
|
||||||
|
itemId
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
return store.dispatch(fetchChangesets(repository, branch)).then(() => {
|
||||||
|
expect(store.getActions()).toEqual(expectedActions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail fetching changesets on error", () => {
|
||||||
|
const itemId = "foo/bar";
|
||||||
|
fetchMock.getOnce(DEFAULT_BRANCH_URL, 500);
|
||||||
|
|
||||||
|
const expectedActions = [
|
||||||
|
{
|
||||||
|
type: FETCH_CHANGESETS_PENDING,
|
||||||
|
itemId
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
return store.dispatch(fetchChangesets(repository)).then(() => {
|
||||||
|
expect(store.getActions()[0]).toEqual(expectedActions[0]);
|
||||||
|
expect(store.getActions()[1].type).toEqual(FETCH_CHANGESETS_FAILURE);
|
||||||
|
expect(store.getActions()[1].payload).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail fetching changesets for specific branch on error", () => {
|
||||||
|
const itemId = "foo/bar/specific";
|
||||||
|
fetchMock.getOnce(SPECIFIC_BRANCH_URL, 500);
|
||||||
|
|
||||||
|
const expectedActions = [
|
||||||
|
{
|
||||||
|
type: FETCH_CHANGESETS_PENDING,
|
||||||
|
itemId
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
return store.dispatch(fetchChangesets(repository, branch)).then(() => {
|
||||||
|
expect(store.getActions()[0]).toEqual(expectedActions[0]);
|
||||||
|
expect(store.getActions()[1].type).toEqual(FETCH_CHANGESETS_FAILURE);
|
||||||
|
expect(store.getActions()[1].payload).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fetch changesets by page", () => {
|
||||||
|
fetchMock.getOnce(DEFAULT_BRANCH_URL + "?page=4", "{}");
|
||||||
|
|
||||||
|
const expectedActions = [
|
||||||
|
{
|
||||||
|
type: FETCH_CHANGESETS_PENDING,
|
||||||
|
itemId: "foo/bar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: FETCH_CHANGESETS_SUCCESS,
|
||||||
|
payload: changesets,
|
||||||
|
itemId: "foo/bar"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
return store
|
||||||
|
.dispatch(fetchChangesets(repository, undefined, 5))
|
||||||
|
.then(() => {
|
||||||
|
expect(store.getActions()).toEqual(expectedActions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fetch changesets by branch and page", () => {
|
||||||
|
fetchMock.getOnce(SPECIFIC_BRANCH_URL + "?page=4", "{}");
|
||||||
|
|
||||||
|
const expectedActions = [
|
||||||
|
{
|
||||||
|
type: FETCH_CHANGESETS_PENDING,
|
||||||
|
itemId: "foo/bar/specific"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: FETCH_CHANGESETS_SUCCESS,
|
||||||
|
payload: changesets,
|
||||||
|
itemId: "foo/bar/specific"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const store = mockStore({});
|
||||||
|
return store.dispatch(fetchChangesets(repository, branch, 5)).then(() => {
|
||||||
|
expect(store.getActions()).toEqual(expectedActions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("changesets reducer", () => {
|
||||||
|
const responseBody = {
|
||||||
|
page: 1,
|
||||||
|
pageTotal: 10,
|
||||||
|
_links: {},
|
||||||
|
_embedded: {
|
||||||
|
changesets: [
|
||||||
|
{ id: "changeset1", author: { mail: "z@phod.com", name: "zaphod" } },
|
||||||
|
{ id: "changeset2", description: "foo" },
|
||||||
|
{ id: "changeset3", description: "bar" }
|
||||||
|
],
|
||||||
|
_embedded: {
|
||||||
|
tags: [],
|
||||||
|
branches: [],
|
||||||
|
parents: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
it("should set state to received changesets", () => {
|
||||||
|
const newState = reducer(
|
||||||
|
{},
|
||||||
|
fetchChangesetsSuccess(repository, undefined, responseBody)
|
||||||
|
);
|
||||||
|
expect(newState).toBeDefined();
|
||||||
|
expect(newState["foo/bar"].byId["changeset1"].author.mail).toEqual(
|
||||||
|
"z@phod.com"
|
||||||
|
);
|
||||||
|
expect(newState["foo/bar"].byId["changeset2"].description).toEqual("foo");
|
||||||
|
expect(newState["foo/bar"].byId["changeset3"].description).toEqual("bar");
|
||||||
|
expect(newState["foo/bar"].list).toEqual({
|
||||||
|
entry: {
|
||||||
|
page: 1,
|
||||||
|
pageTotal: 10,
|
||||||
|
_links: {}
|
||||||
|
},
|
||||||
|
entries: ["changeset1", "changeset2", "changeset3"]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not remove existing changesets", () => {
|
||||||
|
const state = {
|
||||||
|
"foo/bar": {
|
||||||
|
byId: {
|
||||||
|
id2: { id: "id2" },
|
||||||
|
id1: { id: "id1" }
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
entries: ["id1", "id2"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const newState = reducer(
|
||||||
|
state,
|
||||||
|
fetchChangesetsSuccess(repository, undefined, responseBody)
|
||||||
|
);
|
||||||
|
|
||||||
|
const fooBar = newState["foo/bar"];
|
||||||
|
|
||||||
|
expect(fooBar.list.entries).toEqual([
|
||||||
|
"changeset1",
|
||||||
|
"changeset2",
|
||||||
|
"changeset3"
|
||||||
|
]);
|
||||||
|
expect(fooBar.byId["id2"]).toEqual({ id: "id2" });
|
||||||
|
expect(fooBar.byId["id1"]).toEqual({ id: "id1" });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("changeset selectors", () => {
|
||||||
|
const error = new Error("Something went wrong");
|
||||||
|
|
||||||
|
it("should get all changesets for a given repository", () => {
|
||||||
|
const state = {
|
||||||
|
changesets: {
|
||||||
|
"foo/bar": {
|
||||||
|
byId: {
|
||||||
|
id2: { id: "id2" },
|
||||||
|
id1: { id: "id1" }
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
entries: ["id1", "id2"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const result = getChangesets(state, repository);
|
||||||
|
expect(result).toEqual([{ id: "id1" }, { id: "id2" }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true, when fetching changesets is pending", () => {
|
||||||
|
const state = {
|
||||||
|
pending: {
|
||||||
|
[FETCH_CHANGESETS + "/foo/bar"]: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(isFetchChangesetsPending(state, repository)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false, when fetching changesets is not pending", () => {
|
||||||
|
expect(isFetchChangesetsPending({}, repository)).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return error if fetching changesets failed", () => {
|
||||||
|
const state = {
|
||||||
|
failure: {
|
||||||
|
[FETCH_CHANGESETS + "/foo/bar"]: error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getFetchChangesetsFailure(state, repository)).toEqual(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false if fetching changesets did not fail", () => {
|
||||||
|
expect(getFetchChangesetsFailure({}, repository)).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -405,7 +405,7 @@ describe("repos fetch", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should disapatch failure if server returns status code 500", () => {
|
it("should dispatch failure if server returns status code 500", () => {
|
||||||
fetchMock.postOnce(REPOS_URL, {
|
fetchMock.postOnce(REPOS_URL, {
|
||||||
status: 500
|
status: 500
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import {
|
import { Select } from "@scm-manager/ui-components";
|
||||||
Select
|
|
||||||
} from "@scm-manager/ui-components";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
t: string => string,
|
t: string => string,
|
||||||
@@ -15,7 +13,7 @@ type Props = {
|
|||||||
class TypeSelector extends React.Component<Props> {
|
class TypeSelector extends React.Component<Props> {
|
||||||
render() {
|
render() {
|
||||||
const { type, handleTypeChange, loading } = this.props;
|
const { type, handleTypeChange, loading } = this.props;
|
||||||
const types = ["READ", "OWNER", "WRITE"];
|
const types = ["READ", "WRITE", "OWNER"];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@@ -5,12 +5,15 @@ import "../../../../tests/i18n";
|
|||||||
import DeletePermissionButton from "./DeletePermissionButton";
|
import DeletePermissionButton from "./DeletePermissionButton";
|
||||||
|
|
||||||
import { confirmAlert } from "@scm-manager/ui-components";
|
import { confirmAlert } from "@scm-manager/ui-components";
|
||||||
|
import ReactRouterEnzymeContext from "react-router-enzyme-context";
|
||||||
jest.mock("@scm-manager/ui-components", () => ({
|
jest.mock("@scm-manager/ui-components", () => ({
|
||||||
confirmAlert: jest.fn(),
|
confirmAlert: jest.fn(),
|
||||||
DeleteButton: require.requireActual("@scm-manager/ui-components").DeleteButton
|
DeleteButton: require.requireActual("@scm-manager/ui-components").DeleteButton
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe("DeletePermissionButton", () => {
|
describe("DeletePermissionButton", () => {
|
||||||
|
const options = new ReactRouterEnzymeContext();
|
||||||
|
|
||||||
it("should render nothing, if the delete link is missing", () => {
|
it("should render nothing, if the delete link is missing", () => {
|
||||||
const permission = {
|
const permission = {
|
||||||
_links: {}
|
_links: {}
|
||||||
@@ -20,7 +23,8 @@ describe("DeletePermissionButton", () => {
|
|||||||
<DeletePermissionButton
|
<DeletePermissionButton
|
||||||
permission={permission}
|
permission={permission}
|
||||||
deletePermission={() => {}}
|
deletePermission={() => {}}
|
||||||
/>
|
/>,
|
||||||
|
options.get()
|
||||||
);
|
);
|
||||||
expect(navLink.text()).toBe("");
|
expect(navLink.text()).toBe("");
|
||||||
});
|
});
|
||||||
@@ -38,7 +42,8 @@ describe("DeletePermissionButton", () => {
|
|||||||
<DeletePermissionButton
|
<DeletePermissionButton
|
||||||
permission={permission}
|
permission={permission}
|
||||||
deletePermission={() => {}}
|
deletePermission={() => {}}
|
||||||
/>
|
/>,
|
||||||
|
options.get()
|
||||||
);
|
);
|
||||||
expect(navLink.text()).not.toBe("");
|
expect(navLink.text()).not.toBe("");
|
||||||
});
|
});
|
||||||
@@ -56,7 +61,8 @@ describe("DeletePermissionButton", () => {
|
|||||||
<DeletePermissionButton
|
<DeletePermissionButton
|
||||||
permission={permission}
|
permission={permission}
|
||||||
deletePermission={() => {}}
|
deletePermission={() => {}}
|
||||||
/>
|
/>,
|
||||||
|
options.get()
|
||||||
);
|
);
|
||||||
button.find("button").simulate("click");
|
button.find("button").simulate("click");
|
||||||
|
|
||||||
@@ -82,7 +88,8 @@ describe("DeletePermissionButton", () => {
|
|||||||
permission={permission}
|
permission={permission}
|
||||||
confirmDialog={false}
|
confirmDialog={false}
|
||||||
deletePermission={capture}
|
deletePermission={capture}
|
||||||
/>
|
/>,
|
||||||
|
options.get()
|
||||||
);
|
);
|
||||||
button.find("button").simulate("click");
|
button.find("button").simulate("click");
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ class Permissions extends React.Component<Props> {
|
|||||||
{t("permission.group-permission")}
|
{t("permission.group-permission")}
|
||||||
</th>
|
</th>
|
||||||
<th>{t("permission.type")}</th>
|
<th>{t("permission.type")}</th>
|
||||||
|
<th />
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|||||||
1520
scm-ui/yarn.lock
1520
scm-ui/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ public class ManagerDaoAdapter<T extends ModelObject> {
|
|||||||
this.dao = dao;
|
this.dao = dao;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void modify(T object, Function<T, PermissionCheck> permissionCheck, AroundHandler<T> beforeUpdate, AroundHandler<T> afterUpdate) throws NotFoundException {
|
public void modify(T object, Function<T, PermissionCheck> permissionCheck, AroundHandler<T> beforeUpdate, AroundHandler<T> afterUpdate) {
|
||||||
T notModified = dao.get(object.getId());
|
T notModified = dao.get(object.getId());
|
||||||
if (notModified != null) {
|
if (notModified != null) {
|
||||||
permissionCheck.apply(notModified).check();
|
permissionCheck.apply(notModified).check();
|
||||||
@@ -51,7 +51,7 @@ public class ManagerDaoAdapter<T extends ModelObject> {
|
|||||||
return newObject;
|
return newObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void delete(T toDelete, Supplier<PermissionCheck> permissionCheck, AroundHandler<T> beforeDelete, AroundHandler<T> afterDelete) throws NotFoundException {
|
public void delete(T toDelete, Supplier<PermissionCheck> permissionCheck, AroundHandler<T> beforeDelete, AroundHandler<T> afterDelete) {
|
||||||
permissionCheck.get().check();
|
permissionCheck.get().check();
|
||||||
if (dao.contains(toDelete)) {
|
if (dao.contains(toDelete)) {
|
||||||
beforeDelete.handle(toDelete);
|
beforeDelete.handle(toDelete);
|
||||||
|
|||||||
@@ -63,6 +63,6 @@ public class IllegalArgumentExceptionMapper
|
|||||||
public Response toResponse(IllegalArgumentException exception)
|
public Response toResponse(IllegalArgumentException exception)
|
||||||
{
|
{
|
||||||
log.info("caught IllegalArgumentException -- mapping to bad request", exception);
|
log.info("caught IllegalArgumentException -- mapping to bad request", exception);
|
||||||
return Response.status(Status.BAD_REQUEST).build();
|
return Response.status(Status.BAD_REQUEST).entity(exception.getMessage()).build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ public class ChangePasswordResource
|
|||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
||||||
public Response changePassword(@FormParam("old-password") String oldPassword, @FormParam("new-password") String newPassword) throws NotFoundException, ConcurrentModificationException {
|
public Response changePassword(@FormParam("old-password") String oldPassword, @FormParam("new-password") String newPassword) {
|
||||||
AssertUtil.assertIsNotEmpty(oldPassword);
|
AssertUtil.assertIsNotEmpty(oldPassword);
|
||||||
AssertUtil.assertIsNotEmpty(newPassword);
|
AssertUtil.assertIsNotEmpty(newPassword);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||||
|
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||||
|
import org.hibernate.validator.constraints.NotEmpty;
|
||||||
|
import sonia.scm.ReducedModelObject;
|
||||||
|
import sonia.scm.group.GroupManager;
|
||||||
|
import sonia.scm.user.UserManager;
|
||||||
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.validation.constraints.Size;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.QueryParam;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
|
@Path(AutoCompleteResource.PATH)
|
||||||
|
public class AutoCompleteResource {
|
||||||
|
public static final String PATH = "v2/autocomplete/";
|
||||||
|
public static final int MIN_SEARCHED_CHARS = 2;
|
||||||
|
|
||||||
|
public static final String PARAMETER_IS_REQUIRED = "The parameter is required.";
|
||||||
|
public static final String INVALID_PARAMETER_LENGTH = "Invalid parameter length.";
|
||||||
|
|
||||||
|
|
||||||
|
private ReducedObjectModelToDtoMapper mapper;
|
||||||
|
|
||||||
|
private UserManager userManager;
|
||||||
|
private GroupManager groupManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public AutoCompleteResource(ReducedObjectModelToDtoMapper mapper, UserManager userManager, GroupManager groupManager) {
|
||||||
|
this.mapper = mapper;
|
||||||
|
this.userManager = userManager;
|
||||||
|
this.groupManager = groupManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("users")
|
||||||
|
@Produces(VndMediaType.AUTOCOMPLETE)
|
||||||
|
@StatusCodes({
|
||||||
|
@ResponseCode(code = 200, condition = "success"),
|
||||||
|
@ResponseCode(code = 400, condition = "if the searched string contains less than 2 characters"),
|
||||||
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
|
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"user:autocomplete\" privilege"),
|
||||||
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
|
})
|
||||||
|
public List<ReducedObjectModelDto> searchUser(@NotEmpty(message = PARAMETER_IS_REQUIRED) @Size(min = MIN_SEARCHED_CHARS, message = INVALID_PARAMETER_LENGTH) @QueryParam("q") String filter) {
|
||||||
|
return map(userManager.autocomplete(filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("groups")
|
||||||
|
@Produces(VndMediaType.AUTOCOMPLETE)
|
||||||
|
@StatusCodes({
|
||||||
|
@ResponseCode(code = 200, condition = "success"),
|
||||||
|
@ResponseCode(code = 400, condition = "if the searched string contains less than 2 characters"),
|
||||||
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
|
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"group:autocomplete\" privilege"),
|
||||||
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
|
})
|
||||||
|
public List<ReducedObjectModelDto> searchGroup(@NotEmpty(message = PARAMETER_IS_REQUIRED) @Size(min = MIN_SEARCHED_CHARS, message = INVALID_PARAMETER_LENGTH) @QueryParam("q") String filter) {
|
||||||
|
return map(groupManager.autocomplete(filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T extends ReducedModelObject> List<ReducedObjectModelDto> map(Collection<T> autocomplete) {
|
||||||
|
return autocomplete
|
||||||
|
.stream()
|
||||||
|
.map(mapper::map)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -109,9 +109,10 @@ public class BranchRootResource {
|
|||||||
}
|
}
|
||||||
Repository repository = repositoryService.getRepository();
|
Repository repository = repositoryService.getRepository();
|
||||||
RepositoryPermissions.read(repository).check();
|
RepositoryPermissions.read(repository).check();
|
||||||
ChangesetPagingResult changesets = repositoryService.getLogCommand()
|
ChangesetPagingResult changesets = new PagedLogCommandBuilder(repositoryService)
|
||||||
.setPagingStart(page)
|
.page(page)
|
||||||
.setPagingLimit(pageSize)
|
.pageSize(pageSize)
|
||||||
|
.create()
|
||||||
.setBranch(branchName)
|
.setBranch(branchName)
|
||||||
.getChangesets();
|
.getChangesets();
|
||||||
if (changesets != null && changesets.getChangesets() != null) {
|
if (changesets != null && changesets.getChangesets() != null) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import sonia.scm.repository.Repository;
|
|||||||
import sonia.scm.repository.RepositoryNotFoundException;
|
import sonia.scm.repository.RepositoryNotFoundException;
|
||||||
import sonia.scm.repository.RepositoryPermissions;
|
import sonia.scm.repository.RepositoryPermissions;
|
||||||
import sonia.scm.repository.RevisionNotFoundException;
|
import sonia.scm.repository.RevisionNotFoundException;
|
||||||
|
import sonia.scm.repository.api.LogCommandBuilder;
|
||||||
import sonia.scm.repository.api.RepositoryService;
|
import sonia.scm.repository.api.RepositoryService;
|
||||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
@@ -59,9 +60,10 @@ public class ChangesetRootResource {
|
|||||||
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
||||||
Repository repository = repositoryService.getRepository();
|
Repository repository = repositoryService.getRepository();
|
||||||
RepositoryPermissions.read(repository).check();
|
RepositoryPermissions.read(repository).check();
|
||||||
ChangesetPagingResult changesets = repositoryService.getLogCommand()
|
ChangesetPagingResult changesets = new PagedLogCommandBuilder(repositoryService)
|
||||||
.setPagingStart(page)
|
.page(page)
|
||||||
.setPagingLimit(pageSize)
|
.pageSize(pageSize)
|
||||||
|
.create()
|
||||||
.getChangesets();
|
.getChangesets();
|
||||||
if (changesets != null && changesets.getChangesets() != null) {
|
if (changesets != null && changesets.getChangesets() != null) {
|
||||||
PageResult<Changeset> pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal());
|
PageResult<Changeset> pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal());
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public class DiffRootResource {
|
|||||||
@ResponseCode(code = 404, condition = "not found, no revision with the specified param for the repository available or repository not found"),
|
@ResponseCode(code = 404, condition = "not found, no revision with the specified param for the repository available or repository not found"),
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws NotFoundException {
|
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision){
|
||||||
HttpUtil.checkForCRLFInjection(revision);
|
HttpUtil.checkForCRLFInjection(revision);
|
||||||
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
||||||
StreamingOutput responseEntry = output -> {
|
StreamingOutput responseEntry = output -> {
|
||||||
|
|||||||
@@ -73,9 +73,10 @@ public class FileHistoryRootResource {
|
|||||||
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
||||||
log.info("Get changesets of the file {} and revision {}", path, revision);
|
log.info("Get changesets of the file {} and revision {}", path, revision);
|
||||||
Repository repository = repositoryService.getRepository();
|
Repository repository = repositoryService.getRepository();
|
||||||
ChangesetPagingResult changesets = repositoryService.getLogCommand()
|
ChangesetPagingResult changesets = new PagedLogCommandBuilder(repositoryService)
|
||||||
.setPagingStart(page)
|
.page(page)
|
||||||
.setPagingLimit(pageSize)
|
.pageSize(pageSize)
|
||||||
|
.create()
|
||||||
.setPath(path)
|
.setPath(path)
|
||||||
.setStartChangeset(revision)
|
.setStartChangeset(revision)
|
||||||
.getChangesets();
|
.getChangesets();
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public class GroupResource {
|
|||||||
@ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"),
|
@ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"),
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
public Response get(@PathParam("id") String id) throws NotFoundException {
|
public Response get(@PathParam("id") String id){
|
||||||
return adapter.get(id, groupToGroupDtoMapper::map);
|
return adapter.get(id, groupToGroupDtoMapper::map);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ public class GroupResource {
|
|||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||||
public Response update(@PathParam("id") String name, @Valid GroupDto groupDto) throws NotFoundException, ConcurrentModificationException {
|
public Response update(@PathParam("id") String name, @Valid GroupDto groupDto) throws ConcurrentModificationException {
|
||||||
return adapter.update(name, existing -> dtoToGroupMapper.map(groupDto));
|
return adapter.update(name, existing -> dtoToGroupMapper.map(groupDto));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,10 @@ import sonia.scm.AlreadyExistsException;
|
|||||||
import sonia.scm.ConcurrentModificationException;
|
import sonia.scm.ConcurrentModificationException;
|
||||||
import sonia.scm.Manager;
|
import sonia.scm.Manager;
|
||||||
import sonia.scm.ModelObject;
|
import sonia.scm.ModelObject;
|
||||||
import sonia.scm.NotFoundException;
|
|
||||||
import sonia.scm.PageResult;
|
import sonia.scm.PageResult;
|
||||||
|
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
@@ -34,20 +32,11 @@ class IdResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
|||||||
collectionAdapter = new CollectionResourceManagerAdapter<>(manager, type);
|
collectionAdapter = new CollectionResourceManagerAdapter<>(manager, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
Response get(String id, Function<MODEL_OBJECT, DTO> mapToDto) throws NotFoundException {
|
Response get(String id, Function<MODEL_OBJECT, DTO> mapToDto) {
|
||||||
return singleAdapter.get(loadBy(id), mapToDto);
|
return singleAdapter.get(loadBy(id), mapToDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response update(String id, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges, Consumer<MODEL_OBJECT> checker) throws NotFoundException, ConcurrentModificationException {
|
public Response update(String id, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges) throws ConcurrentModificationException {
|
||||||
return singleAdapter.update(
|
|
||||||
loadBy(id),
|
|
||||||
applyChanges,
|
|
||||||
idStaysTheSame(id),
|
|
||||||
checker
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Response update(String id, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges) throws NotFoundException, ConcurrentModificationException {
|
|
||||||
return singleAdapter.update(
|
return singleAdapter.update(
|
||||||
loadBy(id),
|
loadBy(id),
|
||||||
applyChanges,
|
applyChanges,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import de.otto.edison.hal.Link;
|
||||||
import de.otto.edison.hal.Links;
|
import de.otto.edison.hal.Links;
|
||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
import sonia.scm.SCMContextProvider;
|
import sonia.scm.SCMContextProvider;
|
||||||
@@ -8,6 +10,7 @@ import sonia.scm.group.GroupPermissions;
|
|||||||
import sonia.scm.user.UserPermissions;
|
import sonia.scm.user.UserPermissions;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static de.otto.edison.hal.Link.link;
|
import static de.otto.edison.hal.Link.link;
|
||||||
|
|
||||||
@@ -24,6 +27,7 @@ public class IndexDtoGenerator {
|
|||||||
|
|
||||||
public IndexDto generate() {
|
public IndexDto generate() {
|
||||||
Links.Builder builder = Links.linkingTo();
|
Links.Builder builder = Links.linkingTo();
|
||||||
|
List<Link> autoCompleteLinks = Lists.newArrayList();
|
||||||
builder.self(resourceLinks.index().self());
|
builder.self(resourceLinks.index().self());
|
||||||
builder.single(link("uiPlugins", resourceLinks.uiPluginCollection().self()));
|
builder.single(link("uiPlugins", resourceLinks.uiPluginCollection().self()));
|
||||||
if (SecurityUtils.getSubject().isAuthenticated()) {
|
if (SecurityUtils.getSubject().isAuthenticated()) {
|
||||||
@@ -34,6 +38,13 @@ public class IndexDtoGenerator {
|
|||||||
if (UserPermissions.list().isPermitted()) {
|
if (UserPermissions.list().isPermitted()) {
|
||||||
builder.single(link("users", resourceLinks.userCollection().self()));
|
builder.single(link("users", resourceLinks.userCollection().self()));
|
||||||
}
|
}
|
||||||
|
if (UserPermissions.autocomplete().isPermitted()) {
|
||||||
|
autoCompleteLinks.add(Link.linkBuilder("autocomplete", resourceLinks.autoComplete().users()).withName("users").build());
|
||||||
|
}
|
||||||
|
if (GroupPermissions.autocomplete().isPermitted()) {
|
||||||
|
autoCompleteLinks.add(Link.linkBuilder("autocomplete", resourceLinks.autoComplete().groups()).withName("groups").build());
|
||||||
|
}
|
||||||
|
builder.array(autoCompleteLinks);
|
||||||
if (GroupPermissions.list().isPermitted()) {
|
if (GroupPermissions.list().isPermitted()) {
|
||||||
builder.single(link("groups", resourceLinks.groupCollection().self()));
|
builder.single(link("groups", resourceLinks.groupCollection().self()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ public class MapperModule extends AbstractModule {
|
|||||||
bind(FileObjectToFileObjectDtoMapper.class).to(Mappers.getMapper(FileObjectToFileObjectDtoMapper.class).getClass());
|
bind(FileObjectToFileObjectDtoMapper.class).to(Mappers.getMapper(FileObjectToFileObjectDtoMapper.class).getClass());
|
||||||
bind(ModificationsToDtoMapper.class).to(Mappers.getMapper(ModificationsToDtoMapper.class).getClass());
|
bind(ModificationsToDtoMapper.class).to(Mappers.getMapper(ModificationsToDtoMapper.class).getClass());
|
||||||
|
|
||||||
|
bind(ReducedObjectModelToDtoMapper.class).to(Mappers.getMapper(ReducedObjectModelToDtoMapper.class).getClass());
|
||||||
|
|
||||||
// no mapstruct required
|
// no mapstruct required
|
||||||
bind(UIPluginDtoMapper.class);
|
bind(UIPluginDtoMapper.class);
|
||||||
bind(UIPluginDtoCollectionMapper.class);
|
bind(UIPluginDtoCollectionMapper.class);
|
||||||
|
|||||||
@@ -5,14 +5,12 @@ import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
|||||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
import org.apache.shiro.authc.credential.PasswordService;
|
import org.apache.shiro.authc.credential.PasswordService;
|
||||||
import sonia.scm.ConcurrentModificationException;
|
|
||||||
import sonia.scm.NotFoundException;
|
|
||||||
import sonia.scm.user.InvalidPasswordException;
|
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserManager;
|
import sonia.scm.user.UserManager;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.validation.Valid;
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.PUT;
|
import javax.ws.rs.PUT;
|
||||||
@@ -22,9 +20,6 @@ import javax.ws.rs.core.Context;
|
|||||||
import javax.ws.rs.core.Request;
|
import javax.ws.rs.core.Request;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import static sonia.scm.user.InvalidPasswordException.INVALID_MATCHING;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,7 +55,7 @@ public class MeResource {
|
|||||||
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
public Response get(@Context Request request, @Context UriInfo uriInfo) throws NotFoundException {
|
public Response get(@Context Request request, @Context UriInfo uriInfo) {
|
||||||
|
|
||||||
String id = (String) SecurityUtils.getSubject().getPrincipals().getPrimaryPrincipal();
|
String id = (String) SecurityUtils.getSubject().getPrincipals().getPrimaryPrincipal();
|
||||||
return adapter.get(id, meToUserDtoMapper::map);
|
return adapter.get(id, meToUserDtoMapper::map);
|
||||||
@@ -78,19 +73,8 @@ public class MeResource {
|
|||||||
})
|
})
|
||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||||
@Consumes(VndMediaType.PASSWORD_CHANGE)
|
@Consumes(VndMediaType.PASSWORD_CHANGE)
|
||||||
public Response changePassword(PasswordChangeDto passwordChangeDto) throws NotFoundException, ConcurrentModificationException {
|
public Response changePassword(@Valid PasswordChangeDto passwordChangeDto) {
|
||||||
String name = (String) SecurityUtils.getSubject().getPrincipals().getPrimaryPrincipal();
|
userManager.changePasswordForLoggedInUser(passwordService.encryptPassword(passwordChangeDto.getOldPassword()), passwordService.encryptPassword(passwordChangeDto.getNewPassword()));
|
||||||
return adapter.update(name, user -> user.changePassword(passwordService.encryptPassword(passwordChangeDto.getNewPassword())), userManager.getUserTypeChecker().andThen(getOldOriginalPasswordChecker(passwordChangeDto.getOldPassword())));
|
return Response.noContent().build();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Match given old password from the dto with the stored password before updating
|
|
||||||
*/
|
|
||||||
private Consumer<User> getOldOriginalPasswordChecker(String oldPassword) {
|
|
||||||
return user -> {
|
|
||||||
if (!user.getPassword().equals(passwordService.encryptPassword(oldPassword))) {
|
|
||||||
throw new InvalidPasswordException(INVALID_MATCHING);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import sonia.scm.repository.api.LogCommandBuilder;
|
||||||
|
import sonia.scm.repository.api.RepositoryService;
|
||||||
|
|
||||||
|
class PagedLogCommandBuilder {
|
||||||
|
private final RepositoryService repositoryService;
|
||||||
|
private int page;
|
||||||
|
private int pageSize ;
|
||||||
|
|
||||||
|
PagedLogCommandBuilder(RepositoryService repositoryService) {
|
||||||
|
this.repositoryService = repositoryService;
|
||||||
|
}
|
||||||
|
|
||||||
|
PagedLogCommandBuilder page(int page) {
|
||||||
|
this.page = page;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
PagedLogCommandBuilder pageSize(int pageSize) {
|
||||||
|
this.pageSize = pageSize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogCommandBuilder create() {
|
||||||
|
return repositoryService.getLogCommand()
|
||||||
|
.setPagingStart(page * pageSize)
|
||||||
|
.setPagingLimit(pageSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import org.hibernate.validator.constraints.NotEmpty;
|
|||||||
@ToString
|
@ToString
|
||||||
public class PasswordChangeDto {
|
public class PasswordChangeDto {
|
||||||
|
|
||||||
|
@NotEmpty
|
||||||
private String oldPassword;
|
private String oldPassword;
|
||||||
|
|
||||||
@NotEmpty
|
@NotEmpty
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.hibernate.validator.constraints.NotEmpty;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
public class PasswordOverwriteDto {
|
||||||
|
@NotEmpty
|
||||||
|
private String newPassword;
|
||||||
|
}
|
||||||
@@ -100,7 +100,7 @@ public class PermissionRootResource {
|
|||||||
@Produces(VndMediaType.PERMISSION)
|
@Produces(VndMediaType.PERMISSION)
|
||||||
@TypeHint(PermissionDto.class)
|
@TypeHint(PermissionDto.class)
|
||||||
@Path("{permission-name}")
|
@Path("{permission-name}")
|
||||||
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("permission-name") String permissionName) throws NotFoundException {
|
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("permission-name") String permissionName) {
|
||||||
Repository repository = load(namespace, name);
|
Repository repository = load(namespace, name);
|
||||||
RepositoryPermissions.permissionRead(repository).check();
|
RepositoryPermissions.permissionRead(repository).check();
|
||||||
return Response.ok(
|
return Response.ok(
|
||||||
@@ -158,7 +158,7 @@ public class PermissionRootResource {
|
|||||||
public Response update(@PathParam("namespace") String namespace,
|
public Response update(@PathParam("namespace") String namespace,
|
||||||
@PathParam("name") String name,
|
@PathParam("name") String name,
|
||||||
@PathParam("permission-name") String permissionName,
|
@PathParam("permission-name") String permissionName,
|
||||||
@Valid PermissionDto permission) throws NotFoundException, AlreadyExistsException {
|
@Valid PermissionDto permission) throws AlreadyExistsException {
|
||||||
log.info("try to update the permission with name: {}. the modified permission is: {}", permissionName, permission);
|
log.info("try to update the permission with name: {}. the modified permission is: {}", permissionName, permission);
|
||||||
Repository repository = load(namespace, name);
|
Repository repository = load(namespace, name);
|
||||||
RepositoryPermissions.permissionWrite(repository).check();
|
RepositoryPermissions.permissionWrite(repository).check();
|
||||||
@@ -198,7 +198,7 @@ public class PermissionRootResource {
|
|||||||
@Path("{permission-name}")
|
@Path("{permission-name}")
|
||||||
public Response delete(@PathParam("namespace") String namespace,
|
public Response delete(@PathParam("namespace") String namespace,
|
||||||
@PathParam("name") String name,
|
@PathParam("name") String name,
|
||||||
@PathParam("permission-name") String permissionName) throws NotFoundException {
|
@PathParam("permission-name") String permissionName) {
|
||||||
log.info("try to delete the permission with name: {}.", permissionName);
|
log.info("try to delete the permission with name: {}.", permissionName);
|
||||||
Repository repository = load(namespace, name);
|
Repository repository = load(namespace, name);
|
||||||
RepositoryPermissions.modify(repository).check();
|
RepositoryPermissions.modify(repository).check();
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ReducedObjectModelDto extends HalRepresentation {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
private String displayName;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
import sonia.scm.ReducedModelObject;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public abstract class ReducedObjectModelToDtoMapper {
|
||||||
|
|
||||||
|
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||||
|
public abstract ReducedObjectModelDto map(ReducedModelObject modelObject);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -91,7 +91,7 @@ public class RepositoryResource {
|
|||||||
@ResponseCode(code = 404, condition = "not found, no repository with the specified name available in the namespace"),
|
@ResponseCode(code = 404, condition = "not found, no repository with the specified name available in the namespace"),
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name) throws NotFoundException {
|
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name){
|
||||||
return adapter.get(loadBy(namespace, name), repositoryToDtoMapper::map);
|
return adapter.get(loadBy(namespace, name), repositoryToDtoMapper::map);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ public class RepositoryResource {
|
|||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||||
public Response update(@PathParam("namespace") String namespace, @PathParam("name") String name, @Valid RepositoryDto repositoryDto) throws NotFoundException, ConcurrentModificationException {
|
public Response update(@PathParam("namespace") String namespace, @PathParam("name") String name, @Valid RepositoryDto repositoryDto) throws ConcurrentModificationException {
|
||||||
return adapter.update(
|
return adapter.update(
|
||||||
loadBy(namespace, name),
|
loadBy(namespace, name),
|
||||||
existing -> processUpdate(repositoryDto, existing),
|
existing -> processUpdate(repositoryDto, existing),
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ class ResourceLinks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String passwordChange(String name) {
|
public String passwordChange(String name) {
|
||||||
return userLinkBuilder.method("getUserResource").parameters(name).method("changePassword").parameters().href();
|
return userLinkBuilder.method("getUserResource").parameters(name).method("overwritePassword").parameters().href();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,6 +142,26 @@ class ResourceLinks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AutoCompleteLinks autoComplete() {
|
||||||
|
return new AutoCompleteLinks (scmPathInfoStore.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
static class AutoCompleteLinks {
|
||||||
|
private final LinkBuilder linkBuilder;
|
||||||
|
|
||||||
|
AutoCompleteLinks (ScmPathInfo pathInfo) {
|
||||||
|
linkBuilder = new LinkBuilder(pathInfo, AutoCompleteResource.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
String users() {
|
||||||
|
return linkBuilder.method("searchUser").parameters().href();
|
||||||
|
}
|
||||||
|
|
||||||
|
String groups() {
|
||||||
|
return linkBuilder.method("searchGroup").parameters().href();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ConfigLinks config() {
|
ConfigLinks config() {
|
||||||
return new ConfigLinks(scmPathInfoStore.get());
|
return new ConfigLinks(scmPathInfoStore.get());
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user