mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-08 14:35:45 +01:00
merge + add user/group autocomplete ui-components
This commit is contained in:
14
pom.xml
14
pom.xml
@@ -409,8 +409,9 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>com.github.sdorra</groupId>
|
<groupId>com.github.sdorra</groupId>
|
||||||
<artifactId>buildfrontend-maven-plugin</artifactId>
|
<artifactId>buildfrontend-maven-plugin</artifactId>
|
||||||
<version>2.2.0</version>
|
<version>2.3.0</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-javadoc-plugin</artifactId>
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
@@ -432,6 +433,12 @@
|
|||||||
<artifactId>enunciate-maven-plugin</artifactId>
|
<artifactId>enunciate-maven-plugin</artifactId>
|
||||||
<version>${enunciate.version}</version>
|
<version>${enunciate.version}</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>sonia.scm.maven</groupId>
|
||||||
|
<artifactId>smp-maven-plugin</artifactId>
|
||||||
|
<version>1.0.0-alpha-4</version>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</pluginManagement>
|
</pluginManagement>
|
||||||
|
|
||||||
@@ -838,8 +845,8 @@
|
|||||||
<quartz.version>2.2.3</quartz.version>
|
<quartz.version>2.2.3</quartz.version>
|
||||||
|
|
||||||
<!-- frontend -->
|
<!-- frontend -->
|
||||||
<nodejs.version>8.11.4</nodejs.version>
|
<nodejs.version>10.16.0</nodejs.version>
|
||||||
<yarn.version>1.9.4</yarn.version>
|
<yarn.version>1.16.0</yarn.version>
|
||||||
|
|
||||||
<!-- build properties -->
|
<!-- build properties -->
|
||||||
<project.build.javaLevel>1.8</project.build.javaLevel>
|
<project.build.javaLevel>1.8</project.build.javaLevel>
|
||||||
@@ -855,7 +862,6 @@
|
|||||||
<!-- *UserPassword JS files are excluded because extraction of common code would not make the code more readable -->
|
<!-- *UserPassword JS files are excluded because extraction of common code would not make the code more readable -->
|
||||||
<sonar.cpd.exclusions>**/*StoreFactory.java,**/*UserPassword.js</sonar.cpd.exclusions>
|
<sonar.cpd.exclusions>**/*StoreFactory.java,**/*UserPassword.js</sonar.cpd.exclusions>
|
||||||
|
|
||||||
<node.version>8.11.4</node.version>
|
|
||||||
<sonar.nodejs.executable>./scm-ui/target/frontend/buildfrontend-node/node-v${node.version}-linux-x64/bin/node</sonar.nodejs.executable>
|
<sonar.nodejs.executable>./scm-ui/target/frontend/buildfrontend-node/node-v${node.version}-linux-x64/bin/node</sonar.nodejs.executable>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import sonia.scm.repository.InternalRepositoryException;
|
|||||||
import sonia.scm.store.StoreConstants;
|
import sonia.scm.store.StoreConstants;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -28,6 +29,7 @@ import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
|||||||
*
|
*
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
|
@Singleton
|
||||||
public class PathBasedRepositoryLocationResolver extends BasicRepositoryLocationResolver<Path> {
|
public class PathBasedRepositoryLocationResolver extends BasicRepositoryLocationResolver<Path> {
|
||||||
|
|
||||||
public static final String STORE_NAME = "repository-paths";
|
public static final String STORE_NAME = "repository-paths";
|
||||||
@@ -48,7 +50,7 @@ public class PathBasedRepositoryLocationResolver extends BasicRepositoryLocation
|
|||||||
this(contextProvider, initialRepositoryLocationResolver, Clock.systemUTC());
|
this(contextProvider, initialRepositoryLocationResolver, Clock.systemUTC());
|
||||||
}
|
}
|
||||||
|
|
||||||
public PathBasedRepositoryLocationResolver(SCMContextProvider contextProvider, InitialRepositoryLocationResolver initialRepositoryLocationResolver, Clock clock) {
|
PathBasedRepositoryLocationResolver(SCMContextProvider contextProvider, InitialRepositoryLocationResolver initialRepositoryLocationResolver, Clock clock) {
|
||||||
super(Path.class);
|
super(Path.class);
|
||||||
this.contextProvider = contextProvider;
|
this.contextProvider = contextProvider;
|
||||||
this.initialRepositoryLocationResolver = initialRepositoryLocationResolver;
|
this.initialRepositoryLocationResolver = initialRepositoryLocationResolver;
|
||||||
@@ -138,4 +140,8 @@ public class PathBasedRepositoryLocationResolver extends BasicRepositoryLocation
|
|||||||
.resolve(StoreConstants.CONFIG_DIRECTORY_NAME)
|
.resolve(StoreConstants.CONFIG_DIRECTORY_NAME)
|
||||||
.resolve(STORE_NAME.concat(StoreConstants.FILE_EXTENSION));
|
.resolve(STORE_NAME.concat(StoreConstants.FILE_EXTENSION));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void refresh() {
|
||||||
|
this.read();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,4 +198,11 @@ public class XmlRepositoryDAO implements RepositoryDAO {
|
|||||||
public Long getLastModified() {
|
public Long getLastModified() {
|
||||||
return repositoryLocationResolver.getLastModified();
|
return repositoryLocationResolver.getLastModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void refresh() {
|
||||||
|
repositoryLocationResolver.refresh();
|
||||||
|
byNamespaceAndName.clear();
|
||||||
|
byId.clear();
|
||||||
|
init();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import org.junit.jupiter.api.Nested;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junitpioneer.jupiter.TempDirectory;
|
||||||
import org.mockito.ArgumentCaptor;
|
|
||||||
import org.mockito.Captor;
|
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
@@ -32,7 +30,9 @@ import static java.util.Arrays.asList;
|
|||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
import static org.mockito.Mockito.doNothing;
|
import static org.mockito.Mockito.doNothing;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
@@ -47,9 +47,6 @@ class XmlRepositoryDAOTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private PathBasedRepositoryLocationResolver locationResolver;
|
private PathBasedRepositoryLocationResolver locationResolver;
|
||||||
|
|
||||||
@Captor
|
|
||||||
private ArgumentCaptor<BiConsumer<String, Path>> forAllCaptor;
|
|
||||||
|
|
||||||
private FileSystem fileSystem = new DefaultFileSystem();
|
private FileSystem fileSystem = new DefaultFileSystem();
|
||||||
|
|
||||||
private XmlRepositoryDAO dao;
|
private XmlRepositoryDAO dao;
|
||||||
@@ -268,43 +265,80 @@ class XmlRepositoryDAOTest {
|
|||||||
|
|
||||||
verify(locationResolver).updateModificationDate();
|
verify(locationResolver).updateModificationDate();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
private String getXmlFileContent(String id) {
|
||||||
void shouldReadExistingRepositoriesFromPathDatabase(@TempDirectory.TempDir Path basePath) throws IOException {
|
Path storePath = metadataFile(id);
|
||||||
doNothing().when(locationResolver).forAllPaths(forAllCaptor.capture());
|
|
||||||
XmlRepositoryDAO dao = new XmlRepositoryDAO(locationResolver, fileSystem);
|
|
||||||
|
|
||||||
Path repositoryPath = basePath.resolve("existing");
|
assertThat(storePath).isRegularFile();
|
||||||
Files.createDirectories(repositoryPath);
|
return content(storePath);
|
||||||
URL metadataUrl = Resources.getResource("sonia/scm/store/repositoryDaoMetadata.xml");
|
}
|
||||||
Files.copy(metadataUrl.openStream(), repositoryPath.resolve("metadata.xml"));
|
|
||||||
|
|
||||||
forAllCaptor.getValue().accept("existing", repositoryPath);
|
private Path metadataFile(String id) {
|
||||||
|
return locationResolver.create(id).resolve("metadata.xml");
|
||||||
|
}
|
||||||
|
|
||||||
assertThat(dao.contains(new NamespaceAndName("space", "existing"))).isTrue();
|
private String content(Path storePath) {
|
||||||
}
|
try {
|
||||||
|
return new String(Files.readAllBytes(storePath), Charsets.UTF_8);
|
||||||
private String getXmlFileContent(String id) {
|
} catch (IOException e) {
|
||||||
Path storePath = metadataFile(id);
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
assertThat(storePath).isRegularFile();
|
|
||||||
return content(storePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Path metadataFile(String id) {
|
|
||||||
return locationResolver.create(id).resolve("metadata.xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
private String content(Path storePath) {
|
|
||||||
try {
|
|
||||||
return new String(Files.readAllBytes(storePath), Charsets.UTF_8);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Repository createRepository(String id) {
|
@Nested
|
||||||
|
class WithExistingRepositories {
|
||||||
|
|
||||||
|
private Path repositoryPath;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void createMetadataFileForRepository(@TempDirectory.TempDir Path basePath) throws IOException {
|
||||||
|
repositoryPath = basePath.resolve("existing");
|
||||||
|
|
||||||
|
Files.createDirectories(repositoryPath);
|
||||||
|
URL metadataUrl = Resources.getResource("sonia/scm/store/repositoryDaoMetadata.xml");
|
||||||
|
Files.copy(metadataUrl.openStream(), repositoryPath.resolve("metadata.xml"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReadExistingRepositoriesFromPathDatabase() {
|
||||||
|
// given
|
||||||
|
mockExistingPath();
|
||||||
|
|
||||||
|
// when
|
||||||
|
XmlRepositoryDAO dao = new XmlRepositoryDAO(locationResolver, fileSystem);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(dao.contains(new NamespaceAndName("space", "existing"))).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRefreshWithExistingRepositoriesFromPathDatabase() {
|
||||||
|
// given
|
||||||
|
doNothing().when(locationResolver).forAllPaths(any());
|
||||||
|
XmlRepositoryDAO dao = new XmlRepositoryDAO(locationResolver, fileSystem);
|
||||||
|
|
||||||
|
mockExistingPath();
|
||||||
|
|
||||||
|
// when
|
||||||
|
dao.refresh();
|
||||||
|
|
||||||
|
// then
|
||||||
|
verify(locationResolver).refresh();
|
||||||
|
assertThat(dao.contains(new NamespaceAndName("space", "existing"))).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mockExistingPath() {
|
||||||
|
doAnswer(
|
||||||
|
invocation -> {
|
||||||
|
((BiConsumer<String, Path>) invocation.getArgument(0)).accept("existing", repositoryPath);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
).when(locationResolver).forAllPaths(any());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Repository createRepository(String id) {
|
||||||
return new Repository(id, "xml", "space", id);
|
return new Repository(id, "xml", "space", id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,6 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>sonia.scm.maven</groupId>
|
<groupId>sonia.scm.maven</groupId>
|
||||||
<artifactId>smp-maven-plugin</artifactId>
|
<artifactId>smp-maven-plugin</artifactId>
|
||||||
<version>1.0.0-alpha-3</version>
|
|
||||||
<extensions>true</extensions>
|
<extensions>true</extensions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,6 @@
|
|||||||
"@scm-manager/ui-extensions": "^0.1.2"
|
"@scm-manager/ui-extensions": "^0.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.28"
|
"@scm-manager/ui-bundler": "^0.0.29"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.web.lfs;
|
|
||||||
|
|
||||||
import com.github.legman.Subscribe;
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import sonia.scm.EagerSingleton;
|
|
||||||
import sonia.scm.HandlerEventType;
|
|
||||||
import sonia.scm.plugin.Extension;
|
|
||||||
import sonia.scm.repository.GitRepositoryHandler;
|
|
||||||
import sonia.scm.repository.Repository;
|
|
||||||
import sonia.scm.repository.RepositoryEvent;
|
|
||||||
import sonia.scm.store.Blob;
|
|
||||||
import sonia.scm.store.BlobStore;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listener which removes all lfs objects from a blob store, whenever its corresponding git repository gets deleted.
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
* @since 1.54
|
|
||||||
*/
|
|
||||||
@Extension
|
|
||||||
@EagerSingleton
|
|
||||||
public class LfsStoreRemoveListener {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(LfsBlobStoreFactory.class);
|
|
||||||
|
|
||||||
private final LfsBlobStoreFactory lfsBlobStoreFactory;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public LfsStoreRemoveListener(LfsBlobStoreFactory lfsBlobStoreFactory) {
|
|
||||||
this.lfsBlobStoreFactory = lfsBlobStoreFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove all object from the blob store, if the event is an delete event and the repository is a git repository.
|
|
||||||
*
|
|
||||||
* @param event repository event
|
|
||||||
*/
|
|
||||||
@Subscribe
|
|
||||||
public void handleRepositoryEvent(RepositoryEvent event) {
|
|
||||||
if ( isDeleteEvent(event) && isGitRepositoryEvent(event) ) {
|
|
||||||
removeLfsStore(event.getItem());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isDeleteEvent(RepositoryEvent event) {
|
|
||||||
return HandlerEventType.DELETE == event.getEventType();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isGitRepositoryEvent(RepositoryEvent event) {
|
|
||||||
return event.getItem() != null
|
|
||||||
&& event.getItem().getType().equals(GitRepositoryHandler.TYPE_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeLfsStore(Repository repository) {
|
|
||||||
LOG.debug("remove all blobs from store, because corresponding git repository {} was removed", repository.getName());
|
|
||||||
BlobStore blobStore = lfsBlobStoreFactory.getLfsBlobStore(repository);
|
|
||||||
for ( Blob blob : blobStore.getAll() ) {
|
|
||||||
LOG.trace("remove blob {}, because repository {} was removed", blob.getId(), repository.getName());
|
|
||||||
blobStore.remove(blob);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.web.lfs;
|
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import java.util.List;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mockito.InjectMocks;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
|
||||||
import sonia.scm.HandlerEventType;
|
|
||||||
import sonia.scm.repository.Repository;
|
|
||||||
import sonia.scm.repository.RepositoryEvent;
|
|
||||||
import sonia.scm.repository.RepositoryTestData;
|
|
||||||
import sonia.scm.store.Blob;
|
|
||||||
import sonia.scm.store.BlobStore;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit tests for {@link LfsStoreRemoveListener}.
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
|
||||||
public class LfsStoreRemoveListenerTest {
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private LfsBlobStoreFactory lfsBlobStoreFactory;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private BlobStore blobStore;
|
|
||||||
|
|
||||||
@InjectMocks
|
|
||||||
private LfsStoreRemoveListener lfsStoreRemoveListener;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testHandleRepositoryEventWithNonDeleteEvents() {
|
|
||||||
lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEventType.BEFORE_CREATE));
|
|
||||||
lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEventType.CREATE));
|
|
||||||
|
|
||||||
lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEventType.BEFORE_MODIFY));
|
|
||||||
lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEventType.MODIFY));
|
|
||||||
|
|
||||||
lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEventType.BEFORE_DELETE));
|
|
||||||
|
|
||||||
verifyZeroInteractions(lfsBlobStoreFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testHandleRepositoryEventWithNonGitRepositories() {
|
|
||||||
lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEventType.DELETE, "svn"));
|
|
||||||
lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEventType.DELETE, "hg"));
|
|
||||||
lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEventType.DELETE, "dummy"));
|
|
||||||
|
|
||||||
verifyZeroInteractions(lfsBlobStoreFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testHandleRepositoryEvent() {
|
|
||||||
Repository heartOfGold = RepositoryTestData.createHeartOfGold("git");
|
|
||||||
|
|
||||||
when(lfsBlobStoreFactory.getLfsBlobStore(heartOfGold)).thenReturn(blobStore);
|
|
||||||
Blob blobA = mockBlob("a");
|
|
||||||
Blob blobB = mockBlob("b");
|
|
||||||
List<Blob> blobs = Lists.newArrayList(blobA, blobB);
|
|
||||||
when(blobStore.getAll()).thenReturn(blobs);
|
|
||||||
|
|
||||||
|
|
||||||
lfsStoreRemoveListener.handleRepositoryEvent(new RepositoryEvent(HandlerEventType.DELETE, heartOfGold));
|
|
||||||
verify(blobStore).getAll();
|
|
||||||
verify(blobStore).remove(blobA);
|
|
||||||
verify(blobStore).remove(blobB);
|
|
||||||
|
|
||||||
verifyNoMoreInteractions(blobStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Blob mockBlob(String id) {
|
|
||||||
Blob blob = mock(Blob.class);
|
|
||||||
when(blob.getId()).thenReturn(id);
|
|
||||||
return blob;
|
|
||||||
}
|
|
||||||
|
|
||||||
private RepositoryEvent event(HandlerEventType eventType) {
|
|
||||||
return event(eventType, "git");
|
|
||||||
}
|
|
||||||
|
|
||||||
private RepositoryEvent event(HandlerEventType eventType, String repositoryType) {
|
|
||||||
return new RepositoryEvent(eventType, RepositoryTestData.create42Puzzle(repositoryType));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@scm-manager/scm-hg-plugin",
|
"name": "@scm-manager/scm-hg-plugin",
|
||||||
"main": "src/main/js/index.js",
|
"main": "src/main/js/index.js",
|
||||||
"license" : "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "ui-bundler plugin"
|
"build": "ui-bundler plugin"
|
||||||
},
|
},
|
||||||
@@ -9,6 +9,6 @@
|
|||||||
"@scm-manager/ui-extensions": "^0.1.2"
|
"@scm-manager/ui-extensions": "^0.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.28"
|
"@scm-manager/ui-bundler": "^0.0.29"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,6 @@
|
|||||||
"@scm-manager/ui-extensions": "^0.1.2"
|
"@scm-manager/ui-extensions": "^0.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.28"
|
"@scm-manager/ui-bundler": "^0.0.29"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@
|
|||||||
"eslint-fix": "eslint src --fix"
|
"eslint-fix": "eslint src --fix"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.28",
|
"@scm-manager/ui-bundler": "^0.0.29",
|
||||||
"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",
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { AsyncCreatable, Async } from "react-select";
|
|||||||
import type { AutocompleteObject, SelectValue } from "@scm-manager/ui-types";
|
import type { AutocompleteObject, SelectValue } from "@scm-manager/ui-types";
|
||||||
import LabelWithHelpIcon from "./forms/LabelWithHelpIcon";
|
import LabelWithHelpIcon from "./forms/LabelWithHelpIcon";
|
||||||
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
loadSuggestions: string => Promise<AutocompleteObject>,
|
loadSuggestions: string => Promise<AutocompleteObject>,
|
||||||
valueSelected: SelectValue => void,
|
valueSelected: SelectValue => void,
|
||||||
|
|||||||
@@ -10,35 +10,33 @@ type Props = {
|
|||||||
error?: Error
|
error?: Error
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class ErrorNotification extends React.Component<Props> {
|
class ErrorNotification extends React.Component<Props> {
|
||||||
render() {
|
render() {
|
||||||
const { t, error } = this.props;
|
const { t, error } = this.props;
|
||||||
if (error) {
|
if (error) {
|
||||||
if (error instanceof BackendError) {
|
if (error instanceof BackendError) {
|
||||||
return <BackendErrorNotification error={error} />
|
return <BackendErrorNotification error={error} />;
|
||||||
} else if (error instanceof UnauthorizedError) {
|
} else if (error instanceof UnauthorizedError) {
|
||||||
return (
|
return (
|
||||||
<Notification type="danger">
|
<Notification type="danger">
|
||||||
<strong>{t("error-notification.prefix")}:</strong>{" "}
|
<strong>{t("errorNotification.prefix")}:</strong>{" "}
|
||||||
{t("error-notification.timeout")}{" "}
|
{t("errorNotification.timeout")}{" "}
|
||||||
<a href="javascript:window.location.reload(true)">
|
<a href="javascript:window.location.reload(true)">
|
||||||
{t("error-notification.loginLink")}
|
{t("errorNotification.loginLink")}
|
||||||
</a>
|
</a>
|
||||||
</Notification>
|
</Notification>
|
||||||
);
|
);
|
||||||
} else if (error instanceof ForbiddenError) {
|
} else if (error instanceof ForbiddenError) {
|
||||||
return (
|
return (
|
||||||
<Notification type="danger">
|
<Notification type="danger">
|
||||||
<strong>{t("error-notification.prefix")}:</strong>{" "}
|
<strong>{t("errorNotification.prefix")}:</strong>{" "}
|
||||||
{t("error-notification.forbidden")}
|
{t("errorNotification.forbidden")}
|
||||||
</Notification>
|
</Notification>
|
||||||
)
|
);
|
||||||
} else
|
} else {
|
||||||
{
|
|
||||||
return (
|
return (
|
||||||
<Notification type="danger">
|
<Notification type="danger">
|
||||||
<strong>{t("error-notification.prefix")}:</strong> {error.message}
|
<strong>{t("errorNotification.prefix")}:</strong> {error.message}
|
||||||
</Notification>
|
</Notification>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -47,4 +45,4 @@ class ErrorNotification extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translate("commons")(ErrorNotification);
|
export default translate("commons")(ErrorNotification);
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// @flow
|
||||||
|
import React from "react";
|
||||||
|
import { translate } from "react-i18next";
|
||||||
|
import type { SelectValue } from "@scm-manager/ui-types";
|
||||||
|
import UserGroupAutocomplete from "./UserGroupAutocomplete";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
groupAutocompleteLink: string,
|
||||||
|
valueSelected: SelectValue => void,
|
||||||
|
value: string,
|
||||||
|
|
||||||
|
// Context props
|
||||||
|
t: string => string
|
||||||
|
};
|
||||||
|
|
||||||
|
class GroupAutocomplete extends React.Component<Props> {
|
||||||
|
selectName = (selection: SelectValue) => {
|
||||||
|
this.props.valueSelected(selection);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { groupAutocompleteLink, t, value } = this.props;
|
||||||
|
return (
|
||||||
|
<UserGroupAutocomplete
|
||||||
|
autocompleteLink={groupAutocompleteLink}
|
||||||
|
label={t("autocomplete.group")}
|
||||||
|
noOptionsMessage={t("autocomplete.noGroupOptions")}
|
||||||
|
loadingMessage={t("autocomplete.loading")}
|
||||||
|
placeholder={t("autocomplete.groupPlaceholder")}
|
||||||
|
valueSelected={this.selectName}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate("commons")(GroupAutocomplete);
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// @flow
|
||||||
|
import React from "react";
|
||||||
|
import { translate } from "react-i18next";
|
||||||
|
import type { SelectValue } from "@scm-manager/ui-types";
|
||||||
|
import UserGroupAutocomplete from "./UserGroupAutocomplete";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
userAutocompleteLink: string,
|
||||||
|
valueSelected: SelectValue => void,
|
||||||
|
value: string,
|
||||||
|
|
||||||
|
// Context props
|
||||||
|
t: string => string
|
||||||
|
};
|
||||||
|
|
||||||
|
class UserAutocomplete extends React.Component<Props> {
|
||||||
|
selectName = (selection: SelectValue) => {
|
||||||
|
this.props.valueSelected(selection);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { userAutocompleteLink, t, value } = this.props;
|
||||||
|
return (
|
||||||
|
<UserGroupAutocomplete
|
||||||
|
autocompleteLink={userAutocompleteLink}
|
||||||
|
label={t("autocomplete.user")}
|
||||||
|
noOptionsMessage={t("autocomplete.noUserOptions")}
|
||||||
|
loadingMessage={t("autocomplete.loading")}
|
||||||
|
placeholder={t("autocomplete.userPlaceholder")}
|
||||||
|
valueSelected={this.selectName}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate("commons")(UserAutocomplete);
|
||||||
@@ -1,21 +1,18 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { translate } from "react-i18next";
|
|
||||||
import { Autocomplete } from "@scm-manager/ui-components";
|
|
||||||
import type { SelectValue } from "@scm-manager/ui-types";
|
import type { SelectValue } from "@scm-manager/ui-types";
|
||||||
|
import Autocomplete from "./Autocomplete";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
userAutocompleteLink: string,
|
autocompleteLink: string,
|
||||||
valueSelected: SelectValue => void,
|
valueSelected: SelectValue => void,
|
||||||
value: string,
|
value: string,
|
||||||
|
label: string
|
||||||
// Context props
|
|
||||||
t: string => string
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class UserAutocomplete extends React.Component<Props> {
|
class UserGroupAutocomplete extends React.Component<Props> {
|
||||||
loadUserSuggestions = (inputValue: string) => {
|
loadSuggestions = (inputValue: string) => {
|
||||||
const url = this.props.userAutocompleteLink;
|
const url = this.props.autocompleteLink;
|
||||||
const link = url + "?q=";
|
const link = url + "?q=";
|
||||||
return fetch(link + inputValue)
|
return fetch(link + inputValue)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
@@ -37,20 +34,18 @@ class UserAutocomplete extends React.Component<Props> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { t, value } = this.props;
|
const { value, label } = this.props;
|
||||||
return (
|
return (
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
loadSuggestions={this.loadUserSuggestions}
|
loadSuggestions={this.loadSuggestions}
|
||||||
label={t("permission.user")}
|
|
||||||
noOptionsMessage={t("permission.autocomplete.no-user-options")}
|
|
||||||
loadingMessage={t("permission.autocomplete.loading")}
|
|
||||||
placeholder={t("permission.autocomplete.user-placeholder")}
|
|
||||||
valueSelected={this.selectName}
|
valueSelected={this.selectName}
|
||||||
value={value}
|
value={value}
|
||||||
creatable={true}
|
creatable={true}
|
||||||
|
label={label}
|
||||||
|
{...this.props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translate("repos")(UserAutocomplete);
|
export default UserGroupAutocomplete;
|
||||||
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.28"
|
"@scm-manager/ui-bundler": "^0.0.29"
|
||||||
},
|
},
|
||||||
"browserify": {
|
"browserify": {
|
||||||
"transform": [
|
"transform": [
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -54,7 +54,7 @@
|
|||||||
"pre-commit": "jest && flow && eslint src"
|
"pre-commit": "jest && flow && eslint src"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.28",
|
"@scm-manager/ui-bundler": "^0.0.29",
|
||||||
"concat": "^1.0.3",
|
"concat": "^1.0.3",
|
||||||
"copyfiles": "^2.0.0",
|
"copyfiles": "^2.0.0",
|
||||||
"enzyme": "^3.3.0",
|
"enzyme": "^3.3.0",
|
||||||
|
|||||||
@@ -19,11 +19,11 @@
|
|||||||
"subtitle": "Ein unbekannter Fehler ist aufgetreten."
|
"subtitle": "Ein unbekannter Fehler ist aufgetreten."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error-notification": {
|
"errorNotification": {
|
||||||
"prefix": "Fehler",
|
"prefix": "Fehler",
|
||||||
"loginLink": "Erneute Anmeldung",
|
"loginLink": "Erneute Anmeldung",
|
||||||
"timeout": "Die Session ist abgelaufen.",
|
"timeout": "Die Session ist abgelaufen.",
|
||||||
"wrong-login-credentials": "Ungültige Anmeldedaten",
|
"wrongLoginCredentials": "Ungültige Anmeldedaten",
|
||||||
"forbidden": "Sie haben nicht die Berechtigung, diesen Datensatz zu sehen"
|
"forbidden": "Sie haben nicht die Berechtigung, diesen Datensatz zu sehen"
|
||||||
},
|
},
|
||||||
"loading": {
|
"loading": {
|
||||||
@@ -40,6 +40,15 @@
|
|||||||
"config": "Einstellungen"
|
"config": "Einstellungen"
|
||||||
},
|
},
|
||||||
"filterEntries": "Einträge filtern",
|
"filterEntries": "Einträge filtern",
|
||||||
|
"autocomplete": {
|
||||||
|
"group": "Gruppe",
|
||||||
|
"user": "Benutzer",
|
||||||
|
"no-group-options": "Kein Gruppenname als Vorschlag verfügbar",
|
||||||
|
"group-placeholder": "Gruppe eingeben",
|
||||||
|
"no-user-options": "Kein Benutzername als Vorschlag verfügbar",
|
||||||
|
"user-placeholder": "Benutzer eingeben",
|
||||||
|
"loading": "suche..."
|
||||||
|
},
|
||||||
"paginator": {
|
"paginator": {
|
||||||
"next": "Weiter",
|
"next": "Weiter",
|
||||||
"previous": "Zurück"
|
"previous": "Zurück"
|
||||||
|
|||||||
@@ -150,13 +150,6 @@
|
|||||||
"roleHelpText": "READ = read; WRITE = read und write; OWNER = read, write und auch die Möglichkeit Einstellungen und Berechtigungen zu verwalten. Wenn hier nichts angezeigt wird, den Erweitert-Button benutzen, um Details zu sehen.",
|
"roleHelpText": "READ = read; WRITE = read und write; OWNER = read, write und auch die Möglichkeit Einstellungen und Berechtigungen zu verwalten. Wenn hier nichts angezeigt wird, den Erweitert-Button benutzen, um Details zu sehen.",
|
||||||
"permissionsHelpText": "Hier können individuelle Berechtigungen unabhängig von vordefinierten Rollen vergeben werden."
|
"permissionsHelpText": "Hier können individuelle Berechtigungen unabhängig von vordefinierten Rollen vergeben werden."
|
||||||
},
|
},
|
||||||
"autocomplete": {
|
|
||||||
"no-group-options": "Kein Gruppenname als Vorschlag verfügbar",
|
|
||||||
"group-placeholder": "Gruppe eingeben",
|
|
||||||
"no-user-options": "Kein Benutzername als Vorschlag verfügbar",
|
|
||||||
"user-placeholder": "Benutzer eingeben",
|
|
||||||
"loading": "suche..."
|
|
||||||
},
|
|
||||||
"advanced": {
|
"advanced": {
|
||||||
"dialog": {
|
"dialog": {
|
||||||
"title": "Erweiterte Berechtigungen",
|
"title": "Erweiterte Berechtigungen",
|
||||||
|
|||||||
@@ -19,11 +19,11 @@
|
|||||||
"subtitle": "Unknown error occurred"
|
"subtitle": "Unknown error occurred"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error-notification": {
|
"errorNotification": {
|
||||||
"prefix": "Error",
|
"prefix": "Error",
|
||||||
"loginLink": "You can login here again.",
|
"loginLink": "You can login here again.",
|
||||||
"timeout": "The session has expired",
|
"timeout": "The session has expired",
|
||||||
"wrong-login-credentials": "Invalid credentials",
|
"wrongLoginCredentials": "Invalid credentials",
|
||||||
"forbidden": "You don't have permission to view this entity"
|
"forbidden": "You don't have permission to view this entity"
|
||||||
},
|
},
|
||||||
"loading": {
|
"loading": {
|
||||||
@@ -40,6 +40,15 @@
|
|||||||
"config": "Configuration"
|
"config": "Configuration"
|
||||||
},
|
},
|
||||||
"filterEntries": "filter entries",
|
"filterEntries": "filter entries",
|
||||||
|
"autocomplete": {
|
||||||
|
"group": "Group",
|
||||||
|
"user": "User",
|
||||||
|
"noGroupOptions": "No group suggestion available",
|
||||||
|
"groupPlaceholder": "Enter group",
|
||||||
|
"noUserOptions": "No user suggestion available",
|
||||||
|
"userPlaceholder": "Enter user",
|
||||||
|
"loading": "Loading..."
|
||||||
|
},
|
||||||
"paginator": {
|
"paginator": {
|
||||||
"next": "Next",
|
"next": "Next",
|
||||||
"previous": "Previous"
|
"previous": "Previous"
|
||||||
|
|||||||
@@ -153,13 +153,6 @@
|
|||||||
"roleHelpText": "READ = read; WRITE = read and write; OWNER = read, write and also the ability to manage the properties and permissions. If nothing is selected here, use the 'Advanced' Button to see detailed permissions.",
|
"roleHelpText": "READ = read; WRITE = read and write; OWNER = read, write and also the ability to manage the properties and permissions. If nothing is selected here, use the 'Advanced' Button to see detailed permissions.",
|
||||||
"permissionsHelpText": "Use this to specify your own set of permissions regardless of predefined roles."
|
"permissionsHelpText": "Use this to specify your own set of permissions regardless of predefined roles."
|
||||||
},
|
},
|
||||||
"autocomplete": {
|
|
||||||
"no-group-options": "No group suggestion available",
|
|
||||||
"group-placeholder": "Enter group",
|
|
||||||
"no-user-options": "No user suggestion available",
|
|
||||||
"user-placeholder": "Enter user",
|
|
||||||
"loading": "Loading..."
|
|
||||||
},
|
|
||||||
"advanced": {
|
"advanced": {
|
||||||
"dialog": {
|
"dialog": {
|
||||||
"title": "Advanced Permissions",
|
"title": "Advanced Permissions",
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class Login extends React.Component<Props, State> {
|
|||||||
areCredentialsInvalid() {
|
areCredentialsInvalid() {
|
||||||
const { t, error } = this.props;
|
const { t, error } = this.props;
|
||||||
if (error instanceof UnauthorizedError) {
|
if (error instanceof UnauthorizedError) {
|
||||||
return new Error(t("error-notification.wrong-login-credentials"));
|
return new Error(t("errorNotification.wrongLoginCredentials"));
|
||||||
} else {
|
} else {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
// @flow
|
|
||||||
import React from "react";
|
|
||||||
import { translate } from "react-i18next";
|
|
||||||
import { Autocomplete } from "@scm-manager/ui-components";
|
|
||||||
import type { SelectValue } from "@scm-manager/ui-types";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
groupAutocompleteLink: string,
|
|
||||||
valueSelected: SelectValue => void,
|
|
||||||
value: string,
|
|
||||||
|
|
||||||
// Context props
|
|
||||||
t: string => string
|
|
||||||
};
|
|
||||||
|
|
||||||
class GroupAutocomplete extends React.Component<Props> {
|
|
||||||
loadGroupSuggestions = (inputValue: string) => {
|
|
||||||
const url = this.props.groupAutocompleteLink;
|
|
||||||
const link = url + "?q=";
|
|
||||||
return fetch(link + inputValue)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(json => {
|
|
||||||
return json.map(element => {
|
|
||||||
const label = element.displayName
|
|
||||||
? `${element.displayName} (${element.id})`
|
|
||||||
: element.id;
|
|
||||||
return {
|
|
||||||
value: element,
|
|
||||||
label
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
selectName = (selection: SelectValue) => {
|
|
||||||
this.props.valueSelected(selection);
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { t, value } = this.props;
|
|
||||||
return (
|
|
||||||
<Autocomplete
|
|
||||||
loadSuggestions={this.loadGroupSuggestions}
|
|
||||||
label={t("permission.group")}
|
|
||||||
noOptionsMessage={t("permission.autocomplete.no-group-options")}
|
|
||||||
loadingMessage={t("permission.autocomplete.loading")}
|
|
||||||
placeholder={t("permission.autocomplete.group-placeholder")}
|
|
||||||
valueSelected={this.selectName}
|
|
||||||
value={value}
|
|
||||||
creatable={true}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default translate("repos")(GroupAutocomplete);
|
|
||||||
@@ -12,12 +12,12 @@ import {
|
|||||||
SubmitButton,
|
SubmitButton,
|
||||||
Button,
|
Button,
|
||||||
LabelWithHelpIcon,
|
LabelWithHelpIcon,
|
||||||
Radio
|
Radio,
|
||||||
|
GroupAutocomplete,
|
||||||
|
UserAutocomplete
|
||||||
} from "@scm-manager/ui-components";
|
} from "@scm-manager/ui-components";
|
||||||
import * as validator from "../components/permissionValidation";
|
import * as validator from "../components/permissionValidation";
|
||||||
import RoleSelector from "../components/RoleSelector";
|
import RoleSelector from "../components/RoleSelector";
|
||||||
import GroupAutocomplete from "../components/GroupAutocomplete";
|
|
||||||
import UserAutocomplete from "../components/UserAutocomplete";
|
|
||||||
import AdvancedPermissionsDialog from "./AdvancedPermissionsDialog";
|
import AdvancedPermissionsDialog from "./AdvancedPermissionsDialog";
|
||||||
import { findVerbsForRole } from "../modules/permissions";
|
import { findVerbsForRole } from "../modules/permissions";
|
||||||
|
|
||||||
|
|||||||
1022
scm-ui/yarn.lock
1022
scm-ui/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -461,7 +461,6 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>sonia.scm.maven</groupId>
|
<groupId>sonia.scm.maven</groupId>
|
||||||
<artifactId>smp-maven-plugin</artifactId>
|
<artifactId>smp-maven-plugin</artifactId>
|
||||||
<version>1.0.0-alpha-2</version>
|
|
||||||
<configuration>
|
<configuration>
|
||||||
<artifactItems>
|
<artifactItems>
|
||||||
<artifactItem>
|
<artifactItem>
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ public class XmlGroupV1UpdateStep implements UpdateStep {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
XmlGroupV1UpdateStep.V1GroupDatabase v1Database = readV1Database(v1GroupsFile.get());
|
XmlGroupV1UpdateStep.V1GroupDatabase v1Database = readV1Database(v1GroupsFile.get());
|
||||||
v1Database.groupList.groups.forEach(this::update);
|
if (v1Database.groupList != null && v1Database.groupList.groups != null) {
|
||||||
|
v1Database.groupList.groups.forEach(this::update);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import sonia.scm.SCMContextProvider;
|
|||||||
import sonia.scm.migration.UpdateStep;
|
import sonia.scm.migration.UpdateStep;
|
||||||
import sonia.scm.plugin.Extension;
|
import sonia.scm.plugin.Extension;
|
||||||
import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
|
import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
|
||||||
|
import sonia.scm.repository.xml.XmlRepositoryDAO;
|
||||||
import sonia.scm.store.StoreConstants;
|
import sonia.scm.store.StoreConstants;
|
||||||
import sonia.scm.version.Version;
|
import sonia.scm.version.Version;
|
||||||
|
|
||||||
@@ -27,10 +28,12 @@ public class XmlRepositoryFileNameUpdateStep implements UpdateStep {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(XmlRepositoryFileNameUpdateStep.class);
|
private static final Logger LOG = LoggerFactory.getLogger(XmlRepositoryFileNameUpdateStep.class);
|
||||||
|
|
||||||
private final SCMContextProvider contextProvider;
|
private final SCMContextProvider contextProvider;
|
||||||
|
private final XmlRepositoryDAO repositoryDAO;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public XmlRepositoryFileNameUpdateStep(SCMContextProvider contextProvider) {
|
public XmlRepositoryFileNameUpdateStep(SCMContextProvider contextProvider, XmlRepositoryDAO repositoryDAO) {
|
||||||
this.contextProvider = contextProvider;
|
this.contextProvider = contextProvider;
|
||||||
|
this.repositoryDAO = repositoryDAO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -41,6 +44,7 @@ public class XmlRepositoryFileNameUpdateStep implements UpdateStep {
|
|||||||
if (Files.exists(oldRepositoriesFile)) {
|
if (Files.exists(oldRepositoriesFile)) {
|
||||||
LOG.info("moving old repositories database files to repository-paths file");
|
LOG.info("moving old repositories database files to repository-paths file");
|
||||||
Files.move(oldRepositoriesFile, newRepositoryPathsFile);
|
Files.move(oldRepositoriesFile, newRepositoryPathsFile);
|
||||||
|
repositoryDAO.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,12 @@ private final SCMContextProvider contextProvider;
|
|||||||
copyTestDatabaseFile(configDir, fileName);
|
copyTestDatabaseFile(configDir, fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void copyConfigFile(String fileName, String targetFileName) throws IOException {
|
||||||
|
Path configDir = tempDir.resolve("config");
|
||||||
|
Files.createDirectories(configDir);
|
||||||
|
copyTestDatabaseFile(configDir, fileName, targetFileName);
|
||||||
|
}
|
||||||
|
|
||||||
public ConfigurationEntryStore<AssignedPermission> getStoreForConfigFile(String name) {
|
public ConfigurationEntryStore<AssignedPermission> getStoreForConfigFile(String name) {
|
||||||
return storeFactory
|
return storeFactory
|
||||||
.withType(AssignedPermission.class)
|
.withType(AssignedPermission.class)
|
||||||
@@ -59,7 +65,12 @@ private final SCMContextProvider contextProvider;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void copyTestDatabaseFile(Path configDir, String fileName) throws IOException {
|
private void copyTestDatabaseFile(Path configDir, String fileName) throws IOException {
|
||||||
|
Path targetFileName = Paths.get(fileName).getFileName();
|
||||||
|
copyTestDatabaseFile(configDir, fileName, targetFileName.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyTestDatabaseFile(Path configDir, String fileName, String targetFileName) throws IOException {
|
||||||
URL url = Resources.getResource(fileName);
|
URL url = Resources.getResource(fileName);
|
||||||
Files.copy(url.openStream(), configDir.resolve(Paths.get(fileName).getFileName()));
|
Files.copy(url.openStream(), configDir.resolve(targetFileName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,36 @@ class XmlGroupV1UpdateStepTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class WithExistingDatabaseWithEmptyList {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void createGroupV1XML() throws IOException {
|
||||||
|
testUtil.copyConfigFile("sonia/scm/update/group/groups_empty_groups.xml", "groups.xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateNewGroupFromGroupsV1Xml() throws JAXBException {
|
||||||
|
updateStep.doUpdate();
|
||||||
|
verify(groupDAO, times(0)).add(any());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class WithExistingDatabaseWithoutList {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void createGroupV1XML() throws IOException {
|
||||||
|
testUtil.copyConfigFile("sonia/scm/update/group/groups_no_groups.xml", "groups.xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateNewGroupFromGroupsV1Xml() throws JAXBException {
|
||||||
|
updateStep.doUpdate();
|
||||||
|
verify(groupDAO, times(0)).add(any());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldNotFailForMissingConfigDir() throws JAXBException {
|
void shouldNotFailForMissingConfigDir() throws JAXBException {
|
||||||
updateStep.doUpdate();
|
updateStep.doUpdate();
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
|||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junitpioneer.jupiter.TempDirectory;
|
||||||
import sonia.scm.SCMContextProvider;
|
import sonia.scm.SCMContextProvider;
|
||||||
import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
|
import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
|
||||||
|
import sonia.scm.repository.xml.XmlRepositoryDAO;
|
||||||
|
|
||||||
import javax.xml.bind.JAXBException;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -16,12 +16,14 @@ import java.nio.file.Path;
|
|||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith(TempDirectory.class)
|
@ExtendWith(TempDirectory.class)
|
||||||
class XmlRepositoryFileNameUpdateStepTest {
|
class XmlRepositoryFileNameUpdateStepTest {
|
||||||
|
|
||||||
SCMContextProvider contextProvider = mock(SCMContextProvider.class);
|
SCMContextProvider contextProvider = mock(SCMContextProvider.class);
|
||||||
|
XmlRepositoryDAO repositoryDAO = mock(XmlRepositoryDAO.class);
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void mockScmHome(@TempDirectory.TempDir Path tempDir) {
|
void mockScmHome(@TempDirectory.TempDir Path tempDir) {
|
||||||
@@ -29,8 +31,8 @@ class XmlRepositoryFileNameUpdateStepTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldCopyRepositoriesFileToRepositoryPathsFile(@TempDirectory.TempDir Path tempDir) throws JAXBException, IOException {
|
void shouldCopyRepositoriesFileToRepositoryPathsFile(@TempDirectory.TempDir Path tempDir) throws IOException {
|
||||||
XmlRepositoryFileNameUpdateStep updateStep = new XmlRepositoryFileNameUpdateStep(contextProvider);
|
XmlRepositoryFileNameUpdateStep updateStep = new XmlRepositoryFileNameUpdateStep(contextProvider, repositoryDAO);
|
||||||
URL url = Resources.getResource("sonia/scm/update/repository/formerV2RepositoryFile.xml");
|
URL url = Resources.getResource("sonia/scm/update/repository/formerV2RepositoryFile.xml");
|
||||||
Path configDir = tempDir.resolve("config");
|
Path configDir = tempDir.resolve("config");
|
||||||
Files.createDirectories(configDir);
|
Files.createDirectories(configDir);
|
||||||
@@ -40,5 +42,6 @@ class XmlRepositoryFileNameUpdateStepTest {
|
|||||||
|
|
||||||
assertThat(configDir.resolve(PathBasedRepositoryLocationResolver.STORE_NAME + ".xml")).exists();
|
assertThat(configDir.resolve(PathBasedRepositoryLocationResolver.STORE_NAME + ".xml")).exists();
|
||||||
assertThat(configDir.resolve("repositories.xml")).doesNotExist();
|
assertThat(configDir.resolve("repositories.xml")).doesNotExist();
|
||||||
|
verify(repositoryDAO).refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<group-db>
|
||||||
|
<creationTime>1558006904769</creationTime>
|
||||||
|
<groups/>
|
||||||
|
<lastModified>1558007174172</lastModified>
|
||||||
|
</group-db>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<group-db>
|
||||||
|
<creationTime>1558006904769</creationTime>
|
||||||
|
<lastModified>1558007174172</lastModified>
|
||||||
|
</group-db>
|
||||||
Reference in New Issue
Block a user