Merge with default

This commit is contained in:
Rene Pfeuffer
2019-12-05 10:53:33 +01:00
104 changed files with 912 additions and 505 deletions

2
Jenkinsfile vendored
View File

@@ -7,7 +7,7 @@ import com.cloudogu.ces.cesbuildlib.*
node('docker') {
// Change this as when we go back to default - necessary for proper SonarQube analysis
mainBranch = '2.0.0-m3'
mainBranch = 'default'
properties([
// Keep only the last 10 build to preserve space

View File

@@ -11,7 +11,8 @@
"test": "lerna run --scope '@scm-manager/ui-*' test",
"typecheck": "lerna run --scope '@scm-manager/ui-*' typecheck",
"serve": "webpack-dev-server --mode=development --config=scm-ui/ui-scripts/src/webpack.config.js",
"deploy": "ui-scripts publish"
"deploy": "ui-scripts publish",
"set-version": "ui-scripts version"
},
"devDependencies": {
"babel-plugin-reflow": "^0.2.7",

View File

@@ -99,8 +99,9 @@ import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import static javax.lang.model.util.ElementFilter.methodsIn;
/**
*
* @author Sebastian Sdorra
*/
@SupportedAnnotationTypes("*")
@@ -372,6 +373,18 @@ public final class ScmAnnotationProcessor extends AbstractProcessor {
attributes.put(entry.getKey().getSimpleName().toString(),
getValue(entry.getValue()));
}
// add default values
for (ExecutableElement meth : methodsIn(annotationMirror.getAnnotationType().asElement().getEnclosedElements())) {
String attribute = meth.getSimpleName().toString();
AnnotationValue defaultValue = meth.getDefaultValue();
if (defaultValue != null && !attributes.containsKey(attribute)) {
String value = getValue(defaultValue);
if (value != null && !value.isEmpty()) {
attributes.put(attribute, value);
}
}
}
}
}

View File

@@ -8,8 +8,7 @@
<artifactId>scm</artifactId>
<version>2.0.0-SNAPSHOT</version>
</parent>
<groupId>sonia.scm</groupId>
<artifactId>scm-annotations</artifactId>
<version>2.0.0-SNAPSHOT</version>
<name>scm-annotations</name>

View File

@@ -134,7 +134,7 @@ public final class IterableQueue<T> implements Iterable<T>
else
{
logger.trace("create queue iterator");
iterator = new QueueIterator<T>(this);
iterator = new QueueIterator<>(this);
}
return iterator;

View File

@@ -63,7 +63,7 @@ public class LimitedSortedSet<E> extends ForwardingSortedSet<E>
*/
public LimitedSortedSet(int maxSize)
{
this.sortedSet = new TreeSet<E>();
this.sortedSet = new TreeSet<>();
this.maxSize = maxSize;
}

View File

@@ -183,5 +183,5 @@ public abstract class AbstractResourceProcessor implements ResourceProcessor
//~--- fields ---------------------------------------------------------------
/** Field description */
private Map<String, String> variableMap = new HashMap<String, String>();
private Map<String, String> variableMap = new HashMap<>();
}

View File

@@ -52,7 +52,7 @@ public class INIConfiguration
*/
public INIConfiguration()
{
this.sectionMap = new LinkedHashMap<String, INISection>();
this.sectionMap = new LinkedHashMap<>();
}
//~--- methods --------------------------------------------------------------

View File

@@ -55,7 +55,7 @@ public class INISection
public INISection(String name)
{
this.name = name;
this.parameters = new LinkedHashMap<String, String>();
this.parameters = new LinkedHashMap<>();
}
/**

View File

@@ -46,7 +46,7 @@ public final class Base32 extends AbstractBase
{
/** base value */
private static final BigInteger BASE = BigInteger.valueOf(32l);
private static final BigInteger BASE = BigInteger.valueOf(32L);
/** char table */
private static final String CHARS = "0123456789bcdefghjkmnpqrstuvwxyz";

View File

@@ -46,7 +46,7 @@ public final class Base62 extends AbstractBase
{
/** base value */
private static final BigInteger BASE = BigInteger.valueOf(62l);
private static final BigInteger BASE = BigInteger.valueOf(62L);
/** char table */
private static final String CHARS =

View File

@@ -79,7 +79,7 @@ public final class LinkTextParser
public static String parseText(String content)
{
Matcher m = REGEX_URL.matcher(content);
List<Token> tokens = new ArrayList<Token>();
List<Token> tokens = new ArrayList<>();
int position = 0;
String tokenContent = null;

View File

@@ -119,7 +119,7 @@ public final class ServiceUtil
*/
public static <T> List<T> getServices(Class<T> type)
{
List<T> result = new ArrayList<T>();
List<T> result = new ArrayList<>();
try
{

View File

@@ -38,23 +38,12 @@ package sonia.scm.util;
import com.google.common.base.Strings;
import com.google.common.collect.Multimap;
//~--- JDK imports ------------------------------------------------------------
import java.math.BigInteger;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
//~--- JDK imports ------------------------------------------------------------
/**
*
@@ -273,12 +262,12 @@ public final class Util
Comparator<T> comparator, CollectionAppender<T> appender, int start,
int limit)
{
List<T> result = new ArrayList<T>();
List<T> valueList = new ArrayList<T>(values);
List<T> result = new ArrayList<>();
List<T> valueList = new ArrayList<>(values);
if (comparator != null)
{
Collections.sort(valueList, comparator);
valueList.sort(comparator);
}
int length = valueList.size();
@@ -506,12 +495,10 @@ public final class Util
{
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < byteValue.length; i++)
{
int x = byteValue[i] & 0xff;
for (final byte aByteValue : byteValue) {
int x = aByteValue & 0xff;
if (x < 16)
{
if (x < 16) {
buffer.append('0');
}

View File

@@ -66,7 +66,7 @@ public class EnvList
*/
public EnvList()
{
envMap = new HashMap<String, String>();
envMap = new HashMap<>();
}
/**
@@ -77,7 +77,7 @@ public class EnvList
*/
public EnvList(EnvList l)
{
envMap = new HashMap<String, String>(l.envMap);
envMap = new HashMap<>(l.envMap);
}
//~--- methods --------------------------------------------------------------

View File

@@ -470,13 +470,13 @@ public class BufferedHttpServletResponse extends HttpServletResponseWrapper
private ByteArrayPrintWriter pw = null;
/** Field description */
private Set<Cookie> cookies = new HashSet<Cookie>();
private Set<Cookie> cookies = new HashSet<>();
/** Field description */
private int statusCode = HttpServletResponse.SC_OK;
/** Field description */
private Map<String, String> headers = new LinkedHashMap<String, String>();
private Map<String, String> headers = new LinkedHashMap<>();
/** Field description */
private String statusMessage;

View File

@@ -40,28 +40,20 @@ import com.google.common.io.ByteStreams;
import com.google.common.io.Closer;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//~--- JDK imports ------------------------------------------------------------
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.HttpURLConnection;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//~--- JDK imports ------------------------------------------------------------
/**
*
@@ -175,10 +167,8 @@ public class ProxyServlet extends HttpServlet
private void copyContent(HttpURLConnection con, HttpServletResponse response)
throws IOException
{
Closer closer = Closer.create();
try
{
try (Closer closer = Closer.create()) {
InputStream webToProxyBuf =
closer.register(new BufferedInputStream(con.getInputStream()));
OutputStream proxyToClientBuf =
@@ -188,10 +178,6 @@ public class ProxyServlet extends HttpServlet
logger.trace("copied {} bytes for proxy", bytes);
}
finally
{
closer.close();
}
}
/**

View File

@@ -102,7 +102,7 @@ public class XmlMapStringAdapter
public Map<String, String> unmarshal(XmlMapStringElement[] elements)
throws Exception
{
Map<String, String> map = new HashMap<String, String>();
Map<String, String> map = new HashMap<>();
if (elements != null)
{

View File

@@ -90,7 +90,7 @@ public class XmlSetStringAdapter extends XmlAdapter<String, Set<String>>
@Override
public Set<String> unmarshal(String rawString) throws Exception
{
Set<String> tokens = new HashSet<String>();
Set<String> tokens = new HashSet<>();
for (String token : rawString.split(","))
{

View File

@@ -63,7 +63,7 @@ public class IterableQueueTest
@Test(expected = IllegalStateException.class)
public void testDuplicatedEndReached()
{
IterableQueue<String> queue = new IterableQueue<String>();
IterableQueue<String> queue = new IterableQueue<>();
queue.endReached();
queue.endReached();
@@ -76,7 +76,7 @@ public class IterableQueueTest
@Test
public void testIterator()
{
IterableQueue<String> queue = new IterableQueue<String>();
IterableQueue<String> queue = new IterableQueue<>();
assertEquals(QueueIterator.class, queue.iterator().getClass());
queue.endReached();
@@ -120,7 +120,7 @@ public class IterableQueueTest
@Test(expected = IllegalStateException.class)
public void testPushEndReached()
{
IterableQueue<String> queue = new IterableQueue<String>();
IterableQueue<String> queue = new IterableQueue<>();
queue.push("a");
queue.endReached();
@@ -134,7 +134,7 @@ public class IterableQueueTest
@Test
public void testSingleConsumer()
{
final IterableQueue<Integer> queue = new IterableQueue<Integer>();
final IterableQueue<Integer> queue = new IterableQueue<>();
new Thread(new IntegerProducer(queue, false, 100)).start();
assertResult(Lists.newArrayList(queue), 100);
@@ -176,12 +176,12 @@ public class IterableQueueTest
ExecutorService executor = Executors.newFixedThreadPool(threads);
List<Future<List<Integer>>> futures = Lists.newArrayList();
final IterableQueue<Integer> queue = new IterableQueue<Integer>();
final IterableQueue<Integer> queue = new IterableQueue<>();
for (int i = 0; i < consumer; i++)
{
Future<List<Integer>> future =
executor.submit(new CallableQueueCollector<Integer>(queue));
executor.submit(new CallableQueueCollector<>(queue));
futures.add(future);
}

View File

@@ -71,8 +71,8 @@ public class ScmModuleTest
assertThat(
module.getExtensions(),
containsInAnyOrder(
(Class<?>) String.class,
(Class<?>) Integer.class
String.class,
Integer.class
)
);
assertThat(
@@ -86,8 +86,8 @@ public class ScmModuleTest
assertThat(
module.getEvents(),
containsInAnyOrder(
(Class<?>) String.class,
(Class<?>) Boolean.class
String.class,
Boolean.class
)
);
assertThat(
@@ -100,15 +100,15 @@ public class ScmModuleTest
assertThat(
module.getRestProviders(),
containsInAnyOrder(
(Class<?>) Integer.class,
(Class<?>) Long.class
Integer.class,
Long.class
)
);
assertThat(
module.getRestResources(),
containsInAnyOrder(
(Class<?>) Float.class,
(Class<?>) Double.class
Float.class,
Double.class
)
);
//J+

View File

@@ -128,7 +128,7 @@ public class TemplateEngineFactoryTest
assertTrue(engines.contains(engine1));
assertTrue(engines.contains(engine2));
Set<TemplateEngine> ce = new HashSet<TemplateEngine>();
Set<TemplateEngine> ce = new HashSet<>();
ce.add(engine1);
factory = new TemplateEngineFactory(ce, engine2);

View File

@@ -56,7 +56,7 @@ public class UrlBuilderTest
UrlBuilder builder = new UrlBuilder("http://www.short.de");
builder.appendParameter("i", 123).appendParameter("s", "abc");
builder.appendParameter("b", true).appendParameter("l", 321l);
builder.appendParameter("b", true).appendParameter("l", 321L);
assertEquals("http://www.short.de?i=123&s=abc&b=true&l=321", builder.toString());
builder.appendParameter("c", "a b");
assertEquals("http://www.short.de?i=123&s=abc&b=true&l=321&c=a%20b", builder.toString());

View File

@@ -199,7 +199,7 @@ public class XmlGroupDatabase implements XmlDatabase<Group>
/** Field description */
@XmlJavaTypeAdapter(XmlGroupMapAdapter.class)
@XmlElement(name = "groups")
private Map<String, Group> groupMap = new LinkedHashMap<String, Group>();
private Map<String, Group> groupMap = new LinkedHashMap<>();
/** Field description */
private Long lastModified;

View File

@@ -72,7 +72,7 @@ public class XmlGroupList implements Iterable<Group>
*/
public XmlGroupList(Map<String, Group> groupMap)
{
this.groups = new LinkedList<Group>(groupMap.values());
this.groups = new LinkedList<>(groupMap.values());
}
//~--- methods --------------------------------------------------------------

View File

@@ -81,7 +81,7 @@ public class XmlGroupMapAdapter
@Override
public Map<String, Group> unmarshal(XmlGroupList groups) throws Exception
{
Map<String, Group> groupMap = new LinkedHashMap<String, Group>();
Map<String, Group> groupMap = new LinkedHashMap<>();
for (Group group : groups)
{

View File

@@ -202,5 +202,5 @@ public class XmlUserDatabase implements XmlDatabase<User>
/** Field description */
@XmlJavaTypeAdapter(XmlUserMapAdapter.class)
@XmlElement(name = "users")
private Map<String, User> userMap = new LinkedHashMap<String, User>();
private Map<String, User> userMap = new LinkedHashMap<>();
}

View File

@@ -72,7 +72,7 @@ public class XmlUserList implements Iterable<User>
*/
public XmlUserList(Map<String, User> userMap)
{
this.users = new LinkedList<User>(userMap.values());
this.users = new LinkedList<>(userMap.values());
}
//~--- methods --------------------------------------------------------------

View File

@@ -81,7 +81,7 @@ public class XmlUserMapAdapter
@Override
public Map<String, User> unmarshal(XmlUserList users) throws Exception
{
Map<String, User> userMap = new LinkedHashMap<String, User>();
Map<String, User> userMap = new LinkedHashMap<>();
for (User user : users)
{

View File

@@ -52,7 +52,9 @@ public final class XmlStreams {
}
private static XMLStreamReader createReader(Reader reader) throws XMLStreamException {
return XMLInputFactory.newInstance().createXMLStreamReader(reader);
XMLInputFactory factory = XMLInputFactory.newInstance();
factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
return factory.createXMLStreamReader(reader);
}

View File

@@ -14,6 +14,12 @@ const Switcher = styled(ButtonAddons)`
right: 0;
`;
const SmallButton = styled(Button).attrs(props => ({
className: "is-small"
}))`
height: inherit;
`;
type Props = {
repository: Repository;
};
@@ -62,9 +68,9 @@ export default class ProtocolInformation extends React.Component<Props, State> {
}
return (
<Button color={color} action={() => this.selectProtocol(protocol)}>
<SmallButton color={color} action={() => this.selectProtocol(protocol)}>
{name.toUpperCase()}
</Button>
</SmallButton>
);
};

View File

@@ -145,7 +145,7 @@ public class HgPackageReader
*/
private void filterPackage(HgPackages packages)
{
List<HgPackage> pkgList = new ArrayList<HgPackage>();
List<HgPackage> pkgList = new ArrayList<>();
for (HgPackage pkg : packages)
{
@@ -228,7 +228,7 @@ public class HgPackageReader
if (packages == null)
{
packages = new HgPackages();
packages.setPackages(new ArrayList<HgPackage>());
packages.setPackages(new ArrayList<>());
}
return packages;

View File

@@ -211,15 +211,7 @@ public class SVNKitLogger extends SVNDebugLogAdapter
*/
private Logger getLogger(SVNLogType type)
{
Logger logger = loggerMap.get(type);
if (logger == null)
{
logger = LoggerFactory.getLogger(parseName(type.getName()));
loggerMap.put(type, logger);
}
return logger;
return loggerMap.computeIfAbsent(type, t -> LoggerFactory.getLogger(parseName(t.getName())));
}
//~--- fields ---------------------------------------------------------------

View File

@@ -81,15 +81,7 @@ public class MapCacheManager
@Override
public synchronized <K, V> MapCache<K, V> getCache(String name)
{
MapCache<K, V> cache = cacheMap.get(name);
if (cache == null)
{
cache = new MapCache<K, V>();
cacheMap.put(name, cache);
}
return cache;
return (MapCache<K, V>) cacheMap.computeIfAbsent(name, k -> new MapCache<K, V>());
}
//~--- fields ---------------------------------------------------------------

View File

@@ -1,4 +1,5 @@
import React from "react";
import classNames from "classnames";
import { Async, AsyncCreatable } from "react-select";
import { SelectValue } from "@scm-manager/ui-types";
import LabelWithHelpIcon from "./forms/LabelWithHelpIcon";
@@ -14,6 +15,7 @@ type Props = {
loadingMessage: string;
noOptionsMessage: string;
creatable?: boolean;
className?: string;
};
type State = {};
@@ -53,10 +55,11 @@ class Autocomplete extends React.Component<Props, State> {
loadingMessage,
noOptionsMessage,
loadSuggestions,
creatable
creatable,
className
} = this.props;
return (
<div className="field">
<div className={classNames("field", className)}>
<LabelWithHelpIcon label={label} helpText={helpText} />
<div className="control">
{creatable ? (

View File

@@ -336,7 +336,7 @@ exports[`Storyshots DateFromNow Default 1`] = `
exports[`Storyshots Forms|Checkbox Default 1`] = `
<div
className="sc-gipzik xsalO"
className="sc-fBuWsC ldmpJA"
>
<div
className="field"
@@ -381,7 +381,7 @@ exports[`Storyshots Forms|Checkbox Default 1`] = `
exports[`Storyshots Forms|Checkbox Disabled 1`] = `
<div
className="sc-gipzik xsalO"
className="sc-fBuWsC ldmpJA"
>
<div
className="field"
@@ -409,7 +409,7 @@ exports[`Storyshots Forms|Checkbox Disabled 1`] = `
exports[`Storyshots Forms|Radio Default 1`] = `
<div
className="sc-csuQGl fFFkRK"
className="sc-fMiknA keSQNk"
>
<label
className="radio"
@@ -438,7 +438,7 @@ exports[`Storyshots Forms|Radio Default 1`] = `
exports[`Storyshots Forms|Radio Disabled 1`] = `
<div
className="sc-csuQGl fFFkRK"
className="sc-fMiknA keSQNk"
>
<label
className="radio"
@@ -2311,3 +2311,173 @@ PORT_NUMBER =
</pre>
</div>
`;
exports[`Storyshots Table|Table Default 1`] = `
<table
className="sc-jhAzac hmXDXQ table content is-hoverable"
>
<thead>
<tr>
<th
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
First Name
</th>
<th
className="has-cursor-pointer"
onClick={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Last Name
<i
className="fas fa-sort-amount-down has-text-grey-light sc-hzDkRC escBde"
/>
</th>
<th
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
E-Mail
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<h4>
Tricia
</h4>
</td>
<td>
<b
style={
Object {
"color": "red",
}
}
>
McMillan
</b>
</td>
<td>
<a>
tricia@hitchhiker.com
</a>
</td>
</tr>
<tr>
<td>
<h4>
Arthur
</h4>
</td>
<td>
<b
style={
Object {
"color": "red",
}
}
>
Dent
</b>
</td>
<td>
<a>
arthur@hitchhiker.com
</a>
</td>
</tr>
</tbody>
</table>
`;
exports[`Storyshots Table|Table Empty 1`] = `
<div
className="notification is-info"
>
No data found.
</div>
`;
exports[`Storyshots Table|Table TextColumn 1`] = `
<table
className="sc-jhAzac hmXDXQ table content is-hoverable"
>
<thead>
<tr>
<th
className="has-cursor-pointer"
onClick={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Id
<i
className="fas fa-sort-alpha-down has-text-grey-light sc-hzDkRC escBde"
/>
</th>
<th
className="has-cursor-pointer"
onClick={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Name
<i
className="fas fa-sort-alpha-down has-text-grey-light sc-hzDkRC escBde"
/>
</th>
<th
className="has-cursor-pointer"
onClick={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Description
<i
className="fas fa-sort-alpha-down has-text-grey-light sc-hzDkRC escBde"
/>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
21
</td>
<td>
Pommes
</td>
<td>
Fried potato sticks
</td>
</tr>
<tr>
<td>
42
</td>
<td>
Quarter-Pounder
</td>
<td>
Big burger
</td>
</tr>
<tr>
<td>
-84
</td>
<td>
Icecream
</td>
<td>
Cold dessert
</td>
</tr>
</tbody>
</table>
`;

View File

@@ -2,8 +2,8 @@ import React from "react";
type Props = {
displayName: string;
url: string;
disabled: boolean;
url?: string;
disabled?: boolean;
onClick?: () => void;
};

View File

@@ -1,8 +1,7 @@
import React from "react";
import React, { FormEvent } from "react";
import { WithTranslation, withTranslation } from "react-i18next";
import { Links, Link } from "@scm-manager/ui-types";
import { apiClient, SubmitButton, Loading, ErrorNotification } from "../";
import { FormEvent } from "react";
import { apiClient, Level, SubmitButton, Loading, ErrorNotification } from "../";
type RenderProps = {
readOnly: boolean;
@@ -179,7 +178,9 @@ class Configuration extends React.Component<Props, State> {
<form onSubmit={this.modifyConfiguration}>
{this.props.render(renderProps)}
<hr />
<SubmitButton label={t("config.form.submit")} disabled={!valid || readOnly} loading={modifying} />
<Level
right={<SubmitButton label={t("config.form.submit")} disabled={!valid || readOnly} loading={modifying} />}
/>
</form>
</>
);

View File

@@ -1,7 +1,8 @@
import React, { MouseEvent } from "react";
import { AddButton } from "../buttons";
import styled from "styled-components";
import Level from "../layout/Level";
import InputField from "./InputField";
import AddButton from "../buttons/AddButton";
type Props = {
addEntry: (p: string) => void;
@@ -17,6 +18,22 @@ type State = {
entryToAdd: string;
};
const StyledLevel = styled(Level)`
align-items: stretch;
margin-bottom: 1rem !important; // same margin as field
`;
const StyledInputField = styled(InputField)`
width: 100%;
margin-right: 1.5rem;
`;
const StyledField = styled.div.attrs(props => ({
className: "field"
}))`
align-self: flex-end;
`;
class AddEntryToTableField extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
@@ -37,23 +54,29 @@ class AddEntryToTableField extends React.Component<Props, State> {
render() {
const { disabled, buttonLabel, fieldLabel, errorMessage, helpText } = this.props;
return (
<>
<InputField
label={fieldLabel}
errorMessage={errorMessage}
onChange={this.handleAddEntryChange}
validationError={!this.isValid()}
value={this.state.entryToAdd}
onReturnPressed={this.appendEntry}
disabled={disabled}
helpText={helpText}
/>
<AddButton
label={buttonLabel}
action={this.addButtonClicked}
disabled={disabled || this.state.entryToAdd === "" || !this.isValid()}
/>
</>
<StyledLevel
children={
<StyledInputField
label={fieldLabel}
errorMessage={errorMessage}
onChange={this.handleAddEntryChange}
validationError={!this.isValid()}
value={this.state.entryToAdd}
onReturnPressed={this.appendEntry}
disabled={disabled}
helpText={helpText}
/>
}
right={
<StyledField>
<AddButton
label={buttonLabel}
action={this.addButtonClicked}
disabled={disabled || this.state.entryToAdd === "" || !this.isValid()}
/>
</StyledField>
}
/>
);
}

View File

@@ -1,6 +1,7 @@
import React, { MouseEvent } from "react";
import { AutocompleteObject, SelectValue } from "@scm-manager/ui-types";
import styled from "styled-components";
import { SelectValue } from "@scm-manager/ui-types";
import Level from "../layout/Level";
import Autocomplete from "../Autocomplete";
import AddButton from "../buttons/AddButton";
@@ -20,6 +21,11 @@ type State = {
selectedValue?: SelectValue;
};
const StyledAutocomplete = styled(Autocomplete)`
width: 100%;
margin-right: 1.5rem;
`;
class AutocompleteAddEntryToTableField extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
@@ -41,21 +47,26 @@ class AutocompleteAddEntryToTableField extends React.Component<Props, State> {
const { selectedValue } = this.state;
return (
<div className="field">
<Autocomplete
label={fieldLabel}
loadSuggestions={loadSuggestions}
valueSelected={this.handleAddEntryChange}
helpText={helpText}
value={selectedValue}
placeholder={placeholder}
loadingMessage={loadingMessage}
noOptionsMessage={noOptionsMessage}
creatable={true}
/>
<AddButton label={buttonLabel} action={this.addButtonClicked} disabled={disabled} />
</div>
<Level
children={
<StyledAutocomplete
label={fieldLabel}
loadSuggestions={loadSuggestions}
valueSelected={this.handleAddEntryChange}
helpText={helpText}
value={selectedValue}
placeholder={placeholder}
loadingMessage={loadingMessage}
noOptionsMessage={noOptionsMessage}
creatable={true}
/>
}
right={
<div className="field">
<AddButton label={buttonLabel} action={this.addButtonClicked} disabled={disabled} />
</div>
}
/>
);
}

View File

@@ -15,6 +15,7 @@ type Props = {
errorMessage?: string;
disabled?: boolean;
helpText?: string;
className?: string;
};
class InputField extends React.Component<Props> {
@@ -47,11 +48,21 @@ class InputField extends React.Component<Props> {
};
render() {
const { type, placeholder, value, validationError, errorMessage, disabled, label, helpText } = this.props;
const {
type,
placeholder,
value,
validationError,
errorMessage,
disabled,
label,
helpText,
className
} = this.props;
const errorView = validationError ? "is-danger" : "";
const helper = validationError ? <p className="help is-danger">{errorMessage}</p> : "";
return (
<div className="field">
<div className={classNames("field", className)}>
<LabelWithHelpIcon label={label} helpText={helpText} />
<div className="control">
<input

View File

@@ -63,6 +63,7 @@ export * from "./layout";
export * from "./modals";
export * from "./navigation";
export * from "./repos";
export * from "./table";
export {
File,

View File

@@ -4,15 +4,22 @@ import classNames from "classnames";
type Props = {
className?: string;
left?: ReactNode;
children?: ReactNode;
right?: ReactNode;
};
export default class Level extends React.Component<Props> {
render() {
const { className, left, right } = this.props;
const { className, left, children, right } = this.props;
let child = null;
if (children) {
child = <div className="level-item">{children}</div>;
}
return (
<div className={classNames("level", className)}>
<div className="level-left">{left}</div>
{child}
<div className="level-right">{right}</div>
</div>
);

View File

@@ -0,0 +1,18 @@
import React, { FC, ReactNode } from "react";
import { ColumnProps } from "./types";
type Props = ColumnProps & {
children: (row: any, columnIndex: number) => ReactNode;
};
const Column: FC<Props> = ({ row, columnIndex, children }) => {
if (row === undefined) {
throw new Error("missing row, use column only as child of Table");
}
if (columnIndex === undefined) {
throw new Error("missing row, use column only as child of Table");
}
return <>{children(row, columnIndex)}</>;
};
export default Column;

View File

@@ -0,0 +1,19 @@
import React, { FC } from "react";
import styled from "styled-components";
import Icon from "../Icon";
type Props = {
name: string;
isVisible: boolean;
};
const IconWithMarginLeft = styled(Icon)`
visibility: ${(props: Props) => (props.isVisible ? "visible" : "hidden")};
margin-left: 0.25em;
`;
const SortIcon: FC<Props> = (props: Props) => {
return <IconWithMarginLeft name={props.name} isVisible={props.isVisible} />;
};
export default SortIcon;

View File

@@ -0,0 +1,53 @@
import React from "react";
import { storiesOf } from "@storybook/react";
import Table from "./Table";
import Column from "./Column";
import TextColumn from "./TextColumn";
storiesOf("Table|Table", module)
.add("Default", () => (
<Table
data={[
{ firstname: "Tricia", lastname: "McMillan", email: "tricia@hitchhiker.com" },
{ firstname: "Arthur", lastname: "Dent", email: "arthur@hitchhiker.com" }
]}
>
<Column header={"First Name"}>{(row: any) => <h4>{row.firstname}</h4>}</Column>
<Column
header={"Last Name"}
createComparator={() => {
return (a: any, b: any) => {
if (a.lastname > b.lastname) {
return -1;
} else if (a.lastname < b.lastname) {
return 1;
} else {
return 0;
}
};
}}
>
{(row: any) => <b style={{ color: "red" }}>{row.lastname}</b>}
</Column>
<Column header={"E-Mail"}>{(row: any) => <a>{row.email}</a>}</Column>
</Table>
))
.add("TextColumn", () => (
<Table
data={[
{ id: "21", title: "Pommes", desc: "Fried potato sticks" },
{ id: "42", title: "Quarter-Pounder", desc: "Big burger" },
{ id: "-84", title: "Icecream", desc: "Cold dessert" }
]}
>
<TextColumn header="Id" dataKey="id" />
<TextColumn header="Name" dataKey="title" />
<TextColumn header="Description" dataKey="desc" />
</Table>
))
.add("Empty", () => (
<Table data={[]} emptyMessage="No data found.">
<TextColumn header="Id" dataKey="id" />
<TextColumn header="Name" dataKey="name" />
</Table>
));

View File

@@ -0,0 +1,120 @@
import React, { FC, ReactElement, useEffect, useState } from "react";
import styled from "styled-components";
import { Comparator } from "./types";
import SortIcon from "./SortIcon";
import Notification from "../Notification";
const StyledTable = styled.table.attrs(() => ({
className: "table content is-hoverable"
}))``;
type Props = {
data: any[];
sortable?: boolean;
emptyMessage?: string;
children: Array<ReactElement>;
};
const Table: FC<Props> = ({ data, sortable, children, emptyMessage }) => {
const [tableData, setTableData] = useState(data);
useEffect(() => {
setTableData(data);
}, [data]);
const [ascending, setAscending] = useState(false);
const [lastSortBy, setlastSortBy] = useState<number | undefined>();
const [hoveredColumnIndex, setHoveredColumnIndex] = useState<number | undefined>();
const isSortable = (child: ReactElement) => {
return sortable && child.props.createComparator;
};
const sortFunctions: Comparator | undefined[] = [];
React.Children.forEach(children, (child, index) => {
if (child && isSortable(child)) {
sortFunctions.push(child.props.createComparator(child.props, index));
} else {
sortFunctions.push(undefined);
}
});
const mapDataToColumns = (row: any) => {
return (
<tr>
{React.Children.map(children, (child, columnIndex) => {
return <td>{React.cloneElement(child, { ...child.props, columnIndex, row })}</td>;
})}
</tr>
);
};
const sortDescending = (sortAscending: (a: any, b: any) => number) => {
return (a: any, b: any) => {
return sortAscending(a, b) * -1;
};
};
const tableSort = (index: number) => {
const sortFn = sortFunctions[index];
if (!sortFn) {
throw new Error(`column with index ${index} is not sortable`);
}
const sortableData = [...tableData];
let sortOrder = ascending;
if (lastSortBy !== index) {
setAscending(true);
sortOrder = true;
}
const sortFunction = sortOrder ? sortFn : sortDescending(sortFn);
sortableData.sort(sortFunction);
setTableData(sortableData);
setAscending(!sortOrder);
setlastSortBy(index);
};
const shouldShowIcon = (index: number) => {
return index === lastSortBy || index === hoveredColumnIndex;
};
if (!tableData || tableData.length <= 0) {
if (emptyMessage) {
return <Notification type="info">{emptyMessage}</Notification>;
} else {
return null;
}
}
return (
<StyledTable>
<thead>
<tr>
{React.Children.map(children, (child, index) => (
<th
className={isSortable(child) && "has-cursor-pointer"}
onClick={isSortable(child) ? () => tableSort(index) : undefined}
onMouseEnter={() => setHoveredColumnIndex(index)}
onMouseLeave={() => setHoveredColumnIndex(undefined)}
>
{child.props.header}
{isSortable(child) && renderSortIcon(child, ascending, shouldShowIcon(index))}
</th>
))}
</tr>
</thead>
<tbody>{tableData.map(mapDataToColumns)}</tbody>
</StyledTable>
);
};
Table.defaultProps = {
sortable: true
};
const renderSortIcon = (child: ReactElement, ascending: boolean, showIcon: boolean) => {
if (child.props.ascendingIcon && child.props.descendingIcon) {
return <SortIcon name={ascending ? child.props.ascendingIcon : child.props.descendingIcon} isVisible={showIcon} />;
} else {
return <SortIcon name={ascending ? "sort-amount-down-alt" : "sort-amount-down"} isVisible={showIcon} />;
}
};
export default Table;

View File

@@ -0,0 +1,28 @@
import React, { FC } from "react";
import { ColumnProps } from "./types";
type Props = ColumnProps & {
dataKey: string;
};
const TextColumn: FC<Props> = ({ row, dataKey }) => {
return row[dataKey];
};
TextColumn.defaultProps = {
createComparator: (props: Props) => {
return (a: any, b: any) => {
if (a[props.dataKey] < b[props.dataKey]) {
return -1;
} else if (a[props.dataKey] > b[props.dataKey]) {
return 1;
} else {
return 0;
}
};
},
ascendingIcon: "sort-alpha-down-alt",
descendingIcon: "sort-alpha-down"
};
export default TextColumn;

View File

@@ -0,0 +1,4 @@
export { default as Table } from "./Table";
export { default as Column } from "./Column";
export { default as TextColumn } from "./TextColumn";
export { default as SortIcon } from "./SortIcon";

View File

@@ -0,0 +1,12 @@
import { ReactNode } from "react";
export type Comparator = (a: any, b: any) => number;
export type ColumnProps = {
header: ReactNode;
row?: any;
columnIndex?: number;
createComparator?: (props: any, columnIndex: number) => Comparator;
ascendingIcon?: string;
descendingIcon?: string;
};

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env node
const { spawnSync } = require("child_process");
const commands = ["plugin", "plugin-watch", "publish"];
const commands = ["plugin", "plugin-watch", "publish", "version"];
const args = process.argv.slice(2);

View File

@@ -4,7 +4,7 @@ const versions = require("../versions");
const args = process.argv.slice(2);
if (args.length < 1) {
console.log("usage ui-scripts publish version");
console.log("usage ui-scripts publish <version>");
process.exit(1);
}

View File

@@ -0,0 +1,12 @@
const lerna = require("../lerna");
const args = process.argv.slice(2);
if (args.length < 1) {
console.log("usage ui-scripts version <new-version>");
process.exit(1);
}
const version = args[0];
lerna.version(version);

View File

@@ -622,6 +622,10 @@ form .field:not(.is-grouped) {
}
}
.help {
position: absolute;
}
// label with help-icon compensation
.label-icon-spacing {
margin-top: 30px;

View File

@@ -6,7 +6,7 @@
"settingsNavLink": "Einstellungen",
"generalNavLink": "Generell"
},
"info": {
"info": {
"currentAppVersion": "Aktuelle Software-Versionsnummer",
"communityTitle": "Community Support",
"communityIconAlt": "Community Support Icon",
@@ -91,8 +91,7 @@
"submit": "Speichern"
},
"delete": {
"button": "Löschen",
"subtitle": "Berechtigungsrolle löschen",
"button": "Berechtigungsrolle löschen",
"confirmAlert": {
"title": "Berechtigungsrolle löschen?",
"message": "Wollen Sie diese Rolle wirklich löschen? Alle Benutzer mit dieser Rolle verlieren die entsprechenden Berechtigungen.",

View File

@@ -60,8 +60,7 @@
}
},
"deleteGroup": {
"subtitle": "Gruppe löschen",
"button": "Löschen",
"button": "Gruppe löschen",
"confirmAlert": {
"title": "Gruppe löschen",
"message": "Soll die Gruppe wirklich gelöscht werden?",

View File

@@ -114,7 +114,7 @@
"size": "Größe"
},
"noSources": "Keine Sources in diesem Branch gefunden.",
"extension" : {
"extension": {
"notBound": "Keine Erweiterung angebunden."
}
},
@@ -166,8 +166,7 @@
}
},
"deleteRepo": {
"subtitle": "Repository löschen",
"button": "Löschen",
"button": "Repository löschen",
"confirmAlert": {
"title": "Repository löschen",
"message": "Soll das Repository wirklich gelöscht werden?",

View File

@@ -45,8 +45,7 @@
"subtitle": "Erstellen eines neuen Benutzers"
},
"deleteUser": {
"subtitle": "Benutzer löschen",
"button": "Löschen",
"button": "Benutzer löschen",
"confirmAlert": {
"title": "Benutzer löschen",
"message": "Soll der Benutzer wirklich gelöscht werden?",

View File

@@ -11,7 +11,7 @@
"communityTitle": "Community Support",
"communityIconAlt": "Community Support Icon",
"communityInfo": "Contact the SCM-Manager support team for questions about SCM-Manager, to report bugs or to request features through the official channels.",
"communityButton": "Contact our team",
"communityButton": "Contact our Team",
"enterpriseTitle": "Enterprise Support",
"enterpriseIconAlt": "Enterprise Support Icon",
"enterpriseInfo": "You require support with the integration of SCM-Manager into your processes, with the customization of the tool or simply a service level agreement (SLA)?",
@@ -76,8 +76,8 @@
"createSubtitle": "Create Permission Role",
"editSubtitle": "Edit Permission Role",
"overview": {
"title": "Overview of all permission roles",
"noPermissionRoles": "No permission roles found.",
"title": "Overview of all Permission Roles",
"noPermissionRoles": "No Permission Roles found.",
"createButton": "Create Permission Role"
},
"editButton": "Edit",
@@ -91,10 +91,9 @@
"submit": "Save"
},
"delete": {
"button": "Delete",
"subtitle": "Delete permission role",
"button": "Delete Permission Role",
"confirmAlert": {
"title": "Delete permission role",
"title": "Delete Permission Role",
"message": "Do you really want to delete this permission role? All users will lose their corresponding permissions.",
"submit": "Yes",
"cancel": "No"

View File

@@ -60,8 +60,7 @@
}
},
"deleteGroup": {
"subtitle": "Delete Group",
"button": "Delete",
"button": "Delete Group",
"confirmAlert": {
"title": "Delete Group",
"message": "Do you really want to delete the group?",

View File

@@ -114,7 +114,7 @@
"size": "Size"
},
"noSources": "No sources found for this branch.",
"extension" : {
"extension": {
"notBound": "No extension bound."
}
},
@@ -166,8 +166,7 @@
}
},
"deleteRepo": {
"subtitle": "Delete Repository",
"button": "Delete",
"button": "Delete Repository",
"confirmAlert": {
"title": "Delete repository",
"message": "Do you really want to delete the repository?",
@@ -178,7 +177,7 @@
"diff": {
"changes": {
"add": "added",
"delete": "deleted",
"delete": "deleted",
"modify": "modified",
"rename": "renamed",
"copy": "copied"

View File

@@ -45,17 +45,16 @@
"subtitle": "Create a new user"
},
"deleteUser": {
"subtitle": "Delete User",
"button": "Delete",
"button": "Delete User",
"confirmAlert": {
"title": "Delete user",
"title": "Delete User",
"message": "Do you really want to delete the user?",
"submit": "Yes",
"cancel": "No"
}
},
"singleUserPassword": {
"button": "Set password",
"button": "Set Password",
"setPasswordSuccessful": "Password successfully set"
},
"userForm": {

View File

@@ -86,8 +86,7 @@
"submit": "Guardar"
},
"delete": {
"button": "Borrar",
"subtitle": "Eliminar el rol",
"button": "Eliminar el rol",
"confirmAlert": {
"title": "Eliminar el rol",
"message": "¿Realmente desea borrar el rol? Todos los usuarios de este rol perderń sus permisos.",

View File

@@ -60,8 +60,7 @@
}
},
"deleteGroup": {
"subtitle": "Borrar grupo",
"button": "Borrar",
"button": "Borrar grupo",
"confirmAlert": {
"title": "Borrar grupo",
"message": "¿Realmente desea borrar el grupo?",

View File

@@ -114,7 +114,7 @@
"size": "tamaño"
},
"noSources": "No se han encontrado fuentes para esta rama.",
"extension" : {
"extension": {
"notBound": "Sin extensión conectada."
}
},
@@ -166,8 +166,7 @@
}
},
"deleteRepo": {
"subtitle": "Borrar repositorio",
"button": "Borrar",
"button": "Borrar repositorio",
"confirmAlert": {
"title": "Borrar repositorio",
"message": "¿Realmente desea borrar el repositorio?",
@@ -178,7 +177,7 @@
"diff": {
"changes": {
"add": "añadido",
"delete": "borrado",
"delete": "borrado",
"modify": "modificado",
"rename": "renombrado",
"copy": "copiado"

View File

@@ -45,8 +45,7 @@
"subtitle": "Crear un nuevo usuario"
},
"deleteUser": {
"subtitle": "Borrar usuario",
"button": "Borrar",
"button": "Borrar usuario",
"confirmAlert": {
"title": "Borrar usuario",
"message": "¿Realmente desea borrar el usuario?",

View File

@@ -1,7 +1,7 @@
import React from "react";
import { WithTranslation, withTranslation } from "react-i18next";
import { NamespaceStrategies, Config } from "@scm-manager/ui-types";
import { SubmitButton, Notification } from "@scm-manager/ui-components";
import { Level, SubmitButton, Notification } from "@scm-manager/ui-components";
import ProxySettings from "./ProxySettings";
import GeneralSettings from "./GeneralSettings";
import BaseUrlSettings from "./BaseUrlSettings";
@@ -151,10 +151,14 @@ class ConfigForm extends React.Component<Props, State> {
hasUpdatePermission={configUpdatePermission}
/>
<hr />
<SubmitButton
loading={loading}
label={t("config.form.submit")}
disabled={!configUpdatePermission || this.hasError() || !this.state.changed}
<Level
right={
<SubmitButton
loading={loading}
label={t("config.form.submit")}
disabled={!configUpdatePermission || this.hasError() || !this.state.changed}
/>
}
/>
</form>
);

View File

@@ -13,15 +13,13 @@ class AvailableVerbs extends React.Component<Props> {
let verbs = null;
if (role.verbs.length > 0) {
verbs = (
<tr>
<td className="is-paddingless">
<ul>
{role.verbs.map(verb => {
return <li>{t("verbs.repository." + verb + ".displayName")}</li>;
})}
</ul>
</td>
</tr>
<td className="is-paddingless">
<ul>
{role.verbs.map((verb, key) => {
return <li key={key}>{t("verbs.repository." + verb + ".displayName")}</li>;
})}
</ul>
</td>
);
}
return verbs;

View File

@@ -2,7 +2,7 @@ import React from "react";
import { WithTranslation, withTranslation } from "react-i18next";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import { RepositoryRole } from "@scm-manager/ui-types";
import { Button } from "@scm-manager/ui-components";
import { Level, Button } from "@scm-manager/ui-components";
import PermissionRoleDetailsTable from "./PermissionRoleDetailsTable";
type Props = WithTranslation & {
@@ -14,7 +14,12 @@ class PermissionRoleDetails extends React.Component<Props> {
renderEditButton() {
const { t, url } = this.props;
if (!!this.props.role._links.update) {
return <Button label={t("repositoryRole.editButton")} link={`${url}/edit`} color="primary" />;
return (
<>
<hr />
<Level right={<Button label={t("repositoryRole.editButton")} link={`${url}/edit`} color="primary" />} />
</>
);
}
return null;
}
@@ -25,7 +30,6 @@ class PermissionRoleDetails extends React.Component<Props> {
return (
<>
<PermissionRoleDetailsTable role={role} />
<hr />
{this.renderEditButton()}
<ExtensionPoint
name="repositoryRole.role-details.information"

View File

@@ -4,7 +4,7 @@ import { withRouter } from "react-router-dom";
import { WithTranslation, withTranslation } from "react-i18next";
import { History } from "history";
import { RepositoryRole } from "@scm-manager/ui-types";
import { Subtitle, DeleteButton, confirmAlert, ErrorNotification } from "@scm-manager/ui-components";
import { Level, DeleteButton, confirmAlert, ErrorNotification } from "@scm-manager/ui-components";
import { deleteRole, getDeleteRoleFailure, isDeleteRolePending } from "../modules/roles";
type Props = WithTranslation & {
@@ -64,13 +64,9 @@ class DeleteRepositoryRole extends React.Component<Props> {
return (
<>
<Subtitle subtitle={t("repositoryRole.delete.subtitle")} />
<div className="columns">
<div className="column">
<ErrorNotification error={error} />
<DeleteButton label={t("repositoryRole.delete.button")} action={action} loading={loading} />
</div>
</div>
<hr />
<ErrorNotification error={error} />
<Level right={<DeleteButton label={t("repositoryRole.delete.button")} action={action} loading={loading} />} />
</>
);
}

View File

@@ -44,7 +44,6 @@ class EditRepositoryRole extends React.Component<Props> {
<>
<Subtitle subtitle={t("repositoryRole.editSubtitle")} />
<RepositoryRoleForm role={this.props.role} submitForm={role => this.updateRepositoryRole(role)} />
<hr />
<DeleteRepositoryRole role={this.props.role} />
</>
);

View File

@@ -2,10 +2,10 @@ import React from "react";
import { connect } from "react-redux";
import { WithTranslation, withTranslation } from "react-i18next";
import { RepositoryRole } from "@scm-manager/ui-types";
import { InputField, SubmitButton } from "@scm-manager/ui-components";
import { InputField, Level, SubmitButton } from "@scm-manager/ui-components";
import { getRepositoryRolesLink, getRepositoryVerbsLink } from "../../../modules/indexResource";
import { fetchAvailableVerbs, getFetchVerbsFailure, getVerbsFromState, isFetchVerbsPending } from "../modules/roles";
import PermissionCheckbox from "../../../repos/permissions/components/PermissionCheckbox";
import PermissionsWrapper from "../../../permissions/components/PermissionsWrapper";
type Props = WithTranslation & {
role?: RepositoryRole;
@@ -89,16 +89,9 @@ class RepositoryRoleForm extends React.Component<Props, State> {
const { loading, availableVerbs, t } = this.props;
const { role } = this.state;
const verbSelectBoxes = !availableVerbs
? null
: availableVerbs.map(verb => (
<PermissionCheckbox
key={verb}
name={verb}
checked={role.verbs.includes(verb)}
onChange={this.handleVerbChange}
/>
));
const selectedVerbs = availableVerbs
? availableVerbs.reduce((obj, verb) => ({ ...obj, [verb]: role.verbs.includes(verb) }), {})
: {};
return (
<form onSubmit={this.submit}>
@@ -111,16 +104,23 @@ class RepositoryRoleForm extends React.Component<Props, State> {
/>
<div className="field">
<label className="label">{t("repositoryRole.form.permissions")}</label>
{verbSelectBoxes}
<PermissionsWrapper
permissions={selectedVerbs}
onChange={this.handleVerbChange}
disabled={false}
role={true}
/>
</div>
<hr />
<SubmitButton loading={loading} label={t("repositoryRole.form.submit")} disabled={!this.isValid()} />
<Level
right={<SubmitButton loading={loading} label={t("repositoryRole.form.submit")} disabled={!this.isValid()} />}
/>
</form>
);
}
}
const mapStateToProps = (state) => {
const mapStateToProps = state => {
const loading = isFetchVerbsPending(state);
const error = getFetchVerbsFailure(state);
const verbsLink = getRepositoryVerbsLink(state);

View File

@@ -4,6 +4,7 @@ import {
InputField,
Notification,
PasswordConfirmation,
Level,
SubmitButton
} from "@scm-manager/ui-components";
import { WithTranslation, withTranslation } from "react-i18next";
@@ -124,11 +125,7 @@ class ChangeUserPassword extends React.Component<Props, State> {
passwordChanged={this.passwordChanged}
key={this.state.passwordChanged ? "changed" : "unchanged"}
/>
<div className="columns">
<div className="column">
<SubmitButton disabled={!this.isValid()} loading={loading} label={t("password.submit")} />
</div>
</div>
<Level right={<SubmitButton disabled={!this.isValid()} loading={loading} label={t("password.submit")} />} />
</form>
);
}

View File

@@ -8,9 +8,9 @@ import {
InputField,
SubmitButton,
Textarea,
Level,
Checkbox
} from "@scm-manager/ui-components";
import * as validator from "./groupValidation";
type Props = WithTranslation & {
@@ -154,7 +154,7 @@ class GroupForm extends React.Component<Props, State> {
/>
{this.renderExternalField(group)}
{this.renderMemberfields(group)}
<SubmitButton disabled={!this.isValid()} label={t("groupForm.submit")} loading={loading} />
<Level right={<SubmitButton disabled={!this.isValid()} label={t("groupForm.submit")} loading={loading} />} />
</form>
</>
);

View File

@@ -17,9 +17,9 @@ class GroupRow extends React.Component<Props> {
const { group, t } = this.props;
const to = `/group/${group.name}`;
const iconType = group.external ? (
<Icon title={t("group.external")} name="sign-out-alt fa-rotate-270" />
<Icon title={t("group.external")} name="globe-americas" />
) : (
<Icon title={t("group.internal")} name="sign-in-alt fa-rotate-90" />
<Icon title={t("group.internal")} name="home" />
);
return (

View File

@@ -4,7 +4,7 @@ import { withRouter } from "react-router-dom";
import { WithTranslation, withTranslation } from "react-i18next";
import { History } from "history";
import { Group } from "@scm-manager/ui-types";
import { Subtitle, DeleteButton, confirmAlert, ErrorNotification } from "@scm-manager/ui-components";
import { Level, DeleteButton, confirmAlert, ErrorNotification } from "@scm-manager/ui-components";
import { deleteGroup, getDeleteGroupFailure, isDeleteGroupPending } from "../modules/groups";
type Props = WithTranslation & {
@@ -64,13 +64,9 @@ export class DeleteGroup extends React.Component<Props> {
return (
<>
<Subtitle subtitle={t("deleteGroup.subtitle")} />
<hr />
<ErrorNotification error={error} />
<div className="columns">
<div className="column">
<DeleteButton label={t("deleteGroup.button")} action={action} loading={loading} />
</div>
</div>
<Level right={<DeleteButton label={t("deleteGroup.button")} action={action} loading={loading} />} />
</>
);
}

View File

@@ -63,7 +63,6 @@ class EditGroup extends React.Component<Props> {
loading={loading}
loadUserSuggestions={this.loadUserAutocompletion}
/>
<hr />
<DeleteGroup group={group} />
</div>
);

View File

@@ -3,24 +3,34 @@ import { WithTranslation, withTranslation } from "react-i18next";
import { Checkbox } from "@scm-manager/ui-components";
type Props = WithTranslation & {
permission: string;
name: string;
checked: boolean;
onChange: (value: boolean, name: string) => void;
onChange?: (value: boolean, name?: string) => void;
disabled: boolean;
role?: boolean;
};
class PermissionCheckbox extends React.Component<Props> {
render() {
const { t, permission, checked, onChange, disabled } = this.props;
const key = permission.split(":").join(".");
const { name, checked, onChange, disabled, role, t } = this.props;
const key = name.split(":").join(".");
const label = role
? t("verbs.repository." + name + ".displayName")
: this.translateOrDefault("permissions." + key + ".displayName", key);
const helpText = role
? t("verbs.repository." + name + ".description")
: this.translateOrDefault("permissions." + key + ".description", t("permissions.unknown"));
return (
<Checkbox
name={permission}
label={this.translateOrDefault("permissions." + key + ".displayName", key)}
key={name}
name={name}
label={label}
helpText={helpText}
checked={checked}
onChange={onChange}
disabled={disabled}
helpText={this.translateOrDefault("permissions." + key + ".description", t("permissions.unknown"))}
/>
);
}

View File

@@ -0,0 +1,63 @@
import React from "react";
import classNames from "classnames";
import styled from "styled-components";
import PermissionCheckbox from "./PermissionCheckbox";
import { Loading } from "@scm-manager/ui-components";
type Props = {
permissions: {
[key: string]: boolean;
};
onChange: (value: boolean, name: string) => void;
disabled: boolean;
role?: boolean;
};
const StyledWrapper = styled.div`
padding-bottom: 0;
& .field .control {
width: 100%;
word-wrap: break-word;
}
`;
export default class PermissionsWrapper extends React.Component<Props> {
render() {
const { permissions, onChange, disabled, role } = this.props;
if (!permissions) {
return <Loading />;
}
const permissionArray = Object.keys(permissions);
return (
<div className="columns">
<StyledWrapper className={classNames("column", "is-half")}>
{permissionArray.slice(0, permissionArray.length / 2 + 1).map(p => (
<PermissionCheckbox
key={p}
name={p}
checked={permissions[p]}
onChange={onChange}
disabled={disabled}
role={role}
/>
))}
</StyledWrapper>
<StyledWrapper className={classNames("column", "is-half")}>
{permissionArray.slice(permissionArray.length / 2 + 1, permissionArray.length).map(p => (
<PermissionCheckbox
key={p}
name={p}
checked={permissions[p]}
onChange={onChange}
disabled={disabled}
role={role}
/>
))}
</StyledWrapper>
</div>
);
}
}

View File

@@ -1,13 +1,11 @@
import React from "react";
import { connect } from "react-redux";
import { WithTranslation, withTranslation } from "react-i18next";
import classNames from "classnames";
import styled from "styled-components";
import { Link } from "@scm-manager/ui-types";
import { Notification, ErrorNotification, SubmitButton } from "@scm-manager/ui-components";
import { Notification, ErrorNotification, SubmitButton, Level } from "@scm-manager/ui-components";
import { getLink } from "../../modules/indexResource";
import { loadPermissionsForEntity, setPermissions } from "./handlePermissions";
import PermissionCheckbox from "./PermissionCheckbox";
import PermissionsWrapper from "./PermissionsWrapper";
type Props = WithTranslation & {
availablePermissionLink: string;
@@ -25,15 +23,6 @@ type State = {
overwritePermissionsLink?: Link;
};
const PermissionsWrapper = styled.div`
padding-bottom: 0;
& .field .control {
width: 100%;
word-wrap: break-word;
}
`;
class SetPermissions extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
@@ -43,7 +32,6 @@ class SetPermissions extends React.Component<Props, State> {
loading: true,
permissionsChanged: false,
permissionsSubmitted: false,
modifiable: false,
overwritePermissionsLink: undefined
};
}
@@ -125,39 +113,23 @@ class SetPermissions extends React.Component<Props, State> {
<form onSubmit={this.submit}>
{message}
{this.renderPermissions()}
<SubmitButton disabled={!this.state.permissionsChanged} loading={loading} label={t("setPermissions.button")} />
<Level
right={
<SubmitButton
disabled={!this.state.permissionsChanged}
loading={loading}
label={t("setPermissions.button")}
/>
}
/>
</form>
);
}
renderPermissions = () => {
const { overwritePermissionsLink, permissions } = this.state;
const permissionArray = Object.keys(permissions);
return (
<div className="columns">
<PermissionsWrapper className={classNames("column", "is-half")}>
{permissionArray.slice(0, permissionArray.length / 2 + 1).map(p => (
<PermissionCheckbox
key={p}
permission={p}
checked={permissions[p]}
onChange={this.valueChanged}
disabled={!overwritePermissionsLink}
/>
))}
</PermissionsWrapper>
<PermissionsWrapper className={classNames("column", "is-half")}>
{permissionArray.slice(permissionArray.length / 2 + 1, permissionArray.length).map(p => (
<PermissionCheckbox
key={p}
permission={p}
checked={permissions[p]}
onChange={this.valueChanged}
disabled={!overwritePermissionsLink}
/>
))}
</PermissionsWrapper>
</div>
<PermissionsWrapper permissions={permissions} onChange={this.valueChanged} disabled={!overwritePermissionsLink} />
);
};

View File

@@ -2,7 +2,7 @@ import React from "react";
import { WithTranslation, withTranslation } from "react-i18next";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import { Repository, RepositoryType } from "@scm-manager/ui-types";
import { Subtitle, InputField, Select, SubmitButton, Textarea } from "@scm-manager/ui-components";
import { Subtitle, InputField, Select, Textarea, Level, SubmitButton } from "@scm-manager/ui-components";
import * as validator from "./repositoryValidation";
type Props = WithTranslation & {
@@ -52,9 +52,8 @@ class RepositoryForm extends React.Component<Props, State> {
}
}
isFalsy(value) {
isFalsy(value: string) {
return !value;
}
isValid = () => {
@@ -91,7 +90,7 @@ class RepositoryForm extends React.Component<Props, State> {
const disabled = !this.isModifiable() && !this.isCreateMode();
const submitButton = disabled ? null : (
<SubmitButton disabled={!this.isValid()} loading={loading} label={t("repositoryForm.submit")} />
<Level right={<SubmitButton disabled={!this.isValid()} loading={loading} label={t("repositoryForm.submit")} />} />
);
let subtitle = null;

View File

@@ -4,7 +4,7 @@ import { withRouter } from "react-router-dom";
import { WithTranslation, withTranslation } from "react-i18next";
import { History } from "history";
import { Repository } from "@scm-manager/ui-types";
import { Subtitle, DeleteButton, confirmAlert, ErrorNotification } from "@scm-manager/ui-components";
import { Level, DeleteButton, confirmAlert, ErrorNotification } from "@scm-manager/ui-components";
import { deleteRepo, getDeleteRepoFailure, isDeleteRepoPending } from "../modules/repos";
type Props = WithTranslation & {
@@ -65,13 +65,8 @@ class DeleteRepo extends React.Component<Props> {
return (
<>
<hr />
<Subtitle subtitle={t("deleteRepo.subtitle")} />
<ErrorNotification error={error} />
<div className="columns">
<div className="column">
<DeleteButton label={t("deleteRepo.button")} action={action} loading={loading} />
</div>
</div>
<Level right={<DeleteButton label={t("deleteRepo.button")} action={action} loading={loading} />} />
</>
);
}

View File

@@ -1,29 +0,0 @@
import React from "react";
import { WithTranslation, withTranslation } from "react-i18next";
import { Checkbox } from "@scm-manager/ui-components";
type Props = WithTranslation & {
disabled: boolean;
name: string;
checked: boolean;
onChange?: (value: boolean, name?: string) => void;
};
class PermissionCheckbox extends React.Component<Props> {
render() {
const { t } = this.props;
return (
<Checkbox
key={this.props.name}
name={this.props.name}
helpText={t("verbs.repository." + this.props.name + ".description")}
label={t("verbs.repository." + this.props.name + ".displayName")}
checked={this.props.checked}
onChange={this.props.onChange}
disabled={this.props.disabled}
/>
);
}
}
export default withTranslation("plugins")(PermissionCheckbox);

View File

@@ -1,7 +1,7 @@
import React from "react";
import { WithTranslation, withTranslation } from "react-i18next";
import { ButtonGroup, Button, SubmitButton, Modal } from "@scm-manager/ui-components";
import PermissionCheckbox from "../components/PermissionCheckbox";
import PermissionCheckbox from "../../../permissions/components/PermissionCheckbox";
type Props = WithTranslation & {
readOnly: boolean;
@@ -33,7 +33,14 @@ class AdvancedPermissionsDialog extends React.Component<Props, State> {
const { verbs } = this.state;
const verbSelectBoxes = Object.entries(verbs).map(e => (
<PermissionCheckbox key={e[0]} disabled={readOnly} name={e[0]} checked={e[1]} onChange={this.handleChange} />
<PermissionCheckbox
key={e[0]}
name={e[0]}
checked={e[1]}
onChange={this.handleChange}
disabled={readOnly}
role={true}
/>
));
const submitButton = !readOnly ? <SubmitButton label={t("permission.advanced.dialog.submit")} /> : null;

View File

@@ -6,6 +6,7 @@ import {
GroupAutocomplete,
LabelWithHelpIcon,
Radio,
Level,
SubmitButton,
Subtitle,
UserAutocomplete
@@ -141,8 +142,8 @@ class CreatePermissionForm extends React.Component<Props, State> {
</div>
</div>
<div className="columns">
<div className="column is-three-fifths">{this.renderAutocompletionField()}</div>
<div className="column is-two-fifths">
<div className="column is-half">{this.renderAutocompletionField()}</div>
<div className="column is-half">
<div className="columns">
<div className="column is-narrow">
<RoleSelector
@@ -163,15 +164,15 @@ class CreatePermissionForm extends React.Component<Props, State> {
</div>
</div>
</div>
<div className="columns">
<div className="column">
<Level
right={
<SubmitButton
label={t("permission.add-permission.submit-button")}
loading={loading}
disabled={!this.state.valid || this.state.name === ""}
/>
</div>
</div>
}
/>
</form>
</>
);

View File

@@ -18,7 +18,7 @@ class FileButtonAddons extends React.Component<Props> {
};
color = (selected: boolean) => {
return selected ? "link is-selected" : null;
return selected ? "link is-selected" : "";
};
render() {
@@ -27,14 +27,14 @@ class FileButtonAddons extends React.Component<Props> {
return (
<ButtonAddons className={className}>
<div title={t("sources.content.sourcesButton")}>
<Button action={this.showSources} className="reduced" color={this.color(!historyIsSelected)}>
<Button action={this.showSources} color={this.color(!historyIsSelected)}>
<span className="icon">
<i className="fas fa-code" />
</span>
</Button>
</div>
<div title={t("sources.content.historyButton")}>
<Button action={this.showHistory} className="reduced" color={this.color(historyIsSelected)}>
<Button action={this.showHistory} color={this.color(historyIsSelected)}>
<span className="icon">
<i className="fas fa-history" />
</span>

View File

@@ -7,24 +7,25 @@ import { fetchSources, getFetchSourcesFailure, getSources, isFetchSourcesPending
import { connect } from "react-redux";
import { Loading, ErrorNotification } from "@scm-manager/ui-components";
import Notification from "@scm-manager/ui-components/src/Notification";
import {WithTranslation, withTranslation} from "react-i18next";
import { WithTranslation, withTranslation } from "react-i18next";
type Props = WithTranslation & RouteComponentProps & {
repository: Repository;
type Props = WithTranslation &
RouteComponentProps & {
repository: Repository;
// url params
extension: string;
revision?: string;
path?: string;
// url params
extension: string;
revision?: string;
path?: string;
// redux state
loading: boolean;
error?: Error | null;
sources?: File | null;
// redux state
loading: boolean;
error?: Error | null;
sources?: File | null;
// dispatch props
fetchSources: (repository: Repository, revision: string, path: string) => void;
};
// dispatch props
fetchSources: (repository: Repository, revision: string, path: string) => void;
};
const extensionPointName = "repos.sources.extensions";
@@ -32,7 +33,7 @@ class SourceExtensions extends React.Component<Props> {
componentDidMount() {
const { fetchSources, repository, revision, path } = this.props;
// TODO get typing right
fetchSources(repository,revision || "", path || "");
fetchSources(repository, revision || "", path || "");
}
render() {

View File

@@ -1,7 +1,7 @@
import React from "react";
import { WithTranslation, withTranslation } from "react-i18next";
import { User } from "@scm-manager/ui-types";
import { SubmitButton, Notification, ErrorNotification, PasswordConfirmation } from "@scm-manager/ui-components";
import { Level, SubmitButton, Notification, ErrorNotification, PasswordConfirmation } from "@scm-manager/ui-components";
import { setPassword } from "./setPassword";
type Props = WithTranslation & {
@@ -98,15 +98,15 @@ class SetUserPassword extends React.Component<Props, State> {
passwordChanged={this.passwordChanged}
key={this.state.passwordChanged ? "changed" : "unchanged"}
/>
<div className="columns">
<div className="column">
<Level
right={
<SubmitButton
disabled={!this.state.passwordValid}
loading={loading}
label={t("singleUserPassword.button")}
/>
</div>
</div>
}
/>
</form>
);
}

View File

@@ -6,6 +6,7 @@ import {
Checkbox,
InputField,
PasswordConfirmation,
Level,
SubmitButton,
validation as validator
} from "@scm-manager/ui-components";
@@ -166,11 +167,7 @@ class UserForm extends React.Component<Props, State> {
/>
</div>
</div>
<div className="columns">
<div className="column">
<SubmitButton disabled={!this.isValid()} loading={loading} label={t("userForm.button")} />
</div>
</div>
<Level right={<SubmitButton disabled={!this.isValid()} loading={loading} label={t("userForm.button")} />} />
</form>
</>
);

View File

@@ -4,7 +4,7 @@ import { withRouter } from "react-router-dom";
import { WithTranslation, withTranslation } from "react-i18next";
import { History } from "history";
import { User } from "@scm-manager/ui-types";
import { Subtitle, DeleteButton, confirmAlert, ErrorNotification } from "@scm-manager/ui-components";
import { Level, DeleteButton, confirmAlert, ErrorNotification } from "@scm-manager/ui-components";
import { deleteUser, getDeleteUserFailure, isDeleteUserPending } from "../modules/users";
type Props = WithTranslation & {
@@ -64,13 +64,9 @@ class DeleteUser extends React.Component<Props> {
return (
<>
<Subtitle subtitle={t("deleteUser.subtitle")} />
<hr />
<ErrorNotification error={error} />
<div className="columns">
<div className="column">
<DeleteButton label={t("deleteUser.button")} action={action} loading={loading} />
</div>
</div>
<Level right={<DeleteButton label={t("deleteUser.button")} action={action} loading={loading} />} />
</>
);
}

View File

@@ -41,7 +41,6 @@ class EditUser extends React.Component<Props> {
<div>
<ErrorNotification error={error} />
<UserForm submitForm={user => this.modifyUser(user)} user={user} loading={loading} />
<hr />
<DeleteUser user={user} />
</div>
);

View File

@@ -88,14 +88,16 @@ public class BootstrapContextListener extends GuiceServletContextListener {
protected Injector getInjector() {
Throwable startupError = SCMContext.getContext().getStartupError();
if (startupError != null) {
LOG.error("received unrecoverable error during startup", startupError);
return createStageOneInjector(SingleView.error(startupError));
} else if (Versions.isTooOld()) {
LOG.error("Existing version is too old and cannot be migrated to new version. Please update to version {} first", Versions.MIN_VERSION);
LOG.error("existing version is too old and cannot be migrated to new version. Please update to version {} first", Versions.MIN_VERSION);
return createStageOneInjector(SingleView.view("/templates/too-old.mustache", HttpServletResponse.SC_CONFLICT));
} else {
try {
return createStageTwoInjector();
} catch (Exception ex) {
LOG.error("failed to create stage two injector", ex);
return createStageOneInjector(SingleView.error(ex));
}
}

View File

@@ -38,39 +38,27 @@ import com.google.common.base.Strings;
import com.google.common.collect.Multimap;
import com.google.common.io.Closeables;
import com.google.inject.Inject;
import org.apache.shiro.codec.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.net.Proxies;
import sonia.scm.net.TrustAllHostnameVerifier;
import sonia.scm.net.TrustAllTrustManager;
import sonia.scm.util.HttpUtil;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.ProtocolException;
import java.net.Proxy;
import java.net.SocketAddress;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Set;
import javax.inject.Provider;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import java.io.IOException;
import java.io.OutputStream;
import java.net.*;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Set;
//~--- JDK imports ------------------------------------------------------------
/**
* Default implementation of the {@link AdvancedHttpClient}. The default
@@ -324,11 +312,7 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient
sc.init(null, trustAllCerts, new java.security.SecureRandom());
connection.setSSLSocketFactory(sc.getSocketFactory());
}
catch (KeyManagementException ex)
{
logger.error("could not disable certificate validation", ex);
}
catch (NoSuchAlgorithmException ex)
catch (KeyManagementException | NoSuchAlgorithmException ex)
{
logger.error("could not disable certificate validation", ex);
}

View File

@@ -34,20 +34,17 @@ package sonia.scm.net.ahc;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.io.ByteSource;
import sonia.scm.plugin.Extension;
import sonia.scm.util.IOUtil;
//~--- JDK imports ------------------------------------------------------------
import javax.ws.rs.core.MediaType;
import javax.xml.bind.DataBindingException;
import javax.xml.bind.JAXB;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.ws.rs.core.MediaType;
import javax.xml.bind.DataBindingException;
import javax.xml.bind.JAXB;
//~--- JDK imports ------------------------------------------------------------
/**
* {@link ContentTransformer} for xml. The {@link XmlContentTransformer} uses
@@ -96,15 +93,10 @@ public class XmlContentTransformer implements ContentTransformer
stream = content.openBufferedStream();
object = JAXB.unmarshal(stream, type);
}
catch (IOException ex)
catch (IOException | DataBindingException ex)
{
throw new ContentTransformerException("could not unmarshall content", ex);
}
catch (DataBindingException ex)
{
throw new ContentTransformerException("could not unmarshall content", ex);
}
finally
} finally
{
IOUtil.close(stream);
}

View File

@@ -248,6 +248,7 @@ public class DefaultPluginManager implements PluginManager {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
eventBus.post(new RestartEvent(PluginManager.class, cause));
}).start();

View File

@@ -74,7 +74,7 @@ public class MultiParentClassLoader extends ClassLoader
public MultiParentClassLoader(Collection<? extends ClassLoader> parents)
{
super(null);
this.parents = new CopyOnWriteArrayList<ClassLoader>(parents);
this.parents = new CopyOnWriteArrayList<>(parents);
}
//~--- get methods ----------------------------------------------------------

View File

@@ -53,7 +53,7 @@ public final class PluginCenterDto implements Serializable {
private String category;
private String author;
private String avatarUrl;
private String sha256;
private String sha256sum;
@XmlElement(name = "conditions")
private Condition conditions;

View File

@@ -19,7 +19,7 @@ public abstract class PluginCenterDtoMapper {
for (PluginCenterDto.Plugin plugin : pluginCenterDto.getEmbedded().getPlugins()) {
String url = plugin.getLinks().get("download").getHref();
AvailablePluginDescriptor descriptor = new AvailablePluginDescriptor(
map(plugin), map(plugin.getConditions()), plugin.getDependencies(), url, plugin.getSha256()
map(plugin), map(plugin.getConditions()), plugin.getDependencies(), url, plugin.getSha256sum()
);
plugins.add(new AvailablePlugin(descriptor));
}

View File

@@ -128,15 +128,7 @@ public class HealthCheckContextListener implements ServletContextListener
{
// excute health checks for all repsitories asynchronous
SecurityUtils.getSubject().execute(new Runnable()
{
@Override
public void run()
{
healthChecker.checkAll();
}
});
SecurityUtils.getSubject().execute(healthChecker::checkAll);
}
//~--- fields -------------------------------------------------------------

View File

@@ -35,18 +35,14 @@ package sonia.scm.cache;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Predicate;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import sonia.scm.util.IOUtil;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.*;
import org.junit.Assume;
/**
*
@@ -166,14 +162,7 @@ public abstract class CacheTestBase
cache.put("a-1", "test123");
cache.put("a-2", "test123");
Iterable<String> previous = cache.removeAll(new Predicate<String>()
{
@Override
public boolean apply(String item)
{
return item.startsWith("test");
}
});
Iterable<String> previous = cache.removeAll(item -> item != null && item.startsWith("test"));
assertThat(previous, containsInAnyOrder("test123", "test456"));
assertNull(cache.get("test-1"));
@@ -188,8 +177,8 @@ public abstract class CacheTestBase
// skip test if implementation does not support stats
Assume.assumeTrue( stats != null );
assertEquals("test", stats.getName());
assertEquals(0l, stats.getHitCount());
assertEquals(0l, stats.getMissCount());
assertEquals(0L, stats.getHitCount());
assertEquals(0L, stats.getMissCount());
cache.put("test-1", "test123");
cache.put("test-2", "test456");
cache.get("test-1");
@@ -197,11 +186,11 @@ public abstract class CacheTestBase
cache.get("test-1");
cache.get("test-3");
// check that stats have not changed
assertEquals(0l, stats.getHitCount());
assertEquals(0l, stats.getMissCount());
assertEquals(0L, stats.getHitCount());
assertEquals(0L, stats.getMissCount());
stats = cache.getStatistics();
assertEquals(3l, stats.getHitCount());
assertEquals(1l, stats.getMissCount());
assertEquals(3L, stats.getHitCount());
assertEquals(1L, stats.getMissCount());
assertEquals(0.75d, stats.getHitRate(), 0.0d);
assertEquals(0.25d, stats.getMissRate(), 0.0d);
}

Some files were not shown because too many files have changed in this diff Show More