Merge with 2.0.0-m3

This commit is contained in:
René Pfeuffer
2018-11-13 10:36:31 +01:00
35 changed files with 1197 additions and 376 deletions

View File

@@ -37,7 +37,6 @@ import com.github.sdorra.ssp.PermissionObject;
import com.github.sdorra.ssp.StaticPermissions; import com.github.sdorra.ssp.StaticPermissions;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import sonia.scm.BasicPropertiesAware; import sonia.scm.BasicPropertiesAware;
import sonia.scm.ModelObject; import sonia.scm.ModelObject;
import sonia.scm.util.Util; import sonia.scm.util.Util;
@@ -50,8 +49,11 @@ import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.XmlTransient;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
/** /**
* Source code repository. * Source code repository.
@@ -79,7 +81,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
private Long lastModified; private Long lastModified;
private String namespace; private String namespace;
private String name; private String name;
private List<Permission> permissions; private final Set<Permission> permissions = new HashSet<>();
@XmlElement(name = "public") @XmlElement(name = "public")
private boolean publicReadable = false; private boolean publicReadable = false;
private boolean archived = false; private boolean archived = false;
@@ -127,7 +129,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
this.name = name; this.name = name;
this.contact = contact; this.contact = contact;
this.description = description; this.description = description;
this.permissions = Lists.newArrayList();
if (Util.isNotEmpty(permissions)) { if (Util.isNotEmpty(permissions)) {
this.permissions.addAll(Arrays.asList(permissions)); this.permissions.addAll(Arrays.asList(permissions));
@@ -200,12 +201,8 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
return new NamespaceAndName(getNamespace(), getName()); return new NamespaceAndName(getNamespace(), getName());
} }
public List<Permission> getPermissions() { public Collection<Permission> getPermissions() {
if (permissions == null) { return Collections.unmodifiableCollection(permissions);
permissions = Lists.newArrayList();
}
return permissions;
} }
/** /**
@@ -300,8 +297,17 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
this.name = name; this.name = name;
} }
public void setPermissions(List<Permission> permissions) { public void setPermissions(Collection<Permission> permissions) {
this.permissions = permissions; this.permissions.clear();
this.permissions.addAll(permissions);
}
public void addPermission(Permission newPermission) {
this.permissions.add(newPermission);
}
public void removePermission(Permission permission) {
this.permissions.remove(permission);
} }
public void setPublicReadable(boolean publicReadable) { public void setPublicReadable(boolean publicReadable) {

View File

@@ -32,6 +32,7 @@
package sonia.scm.it; package sonia.scm.it;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
import org.assertj.core.api.Assertions;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
@@ -49,8 +50,10 @@ import sonia.scm.web.VndMediaType;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static sonia.scm.it.utils.RepositoryUtil.addAndCommitRandomFile; import static sonia.scm.it.utils.RepositoryUtil.addAndCommitRandomFile;
@@ -72,7 +75,7 @@ public class PermissionsITCase {
public TemporaryFolder temporaryFolder = new TemporaryFolder(); public TemporaryFolder temporaryFolder = new TemporaryFolder();
private final String repositoryType; private final String repositoryType;
private int createdPermissions; private Collection<String> createdPermissions;
public PermissionsITCase(String repositoryType) { public PermissionsITCase(String repositoryType) {
@@ -94,7 +97,7 @@ public class PermissionsITCase {
TestData.createNotAdminUser(USER_OWNER, USER_PASS); TestData.createNotAdminUser(USER_OWNER, USER_PASS);
TestData.createUserPermission(USER_OWNER, PermissionType.OWNER, repositoryType); TestData.createUserPermission(USER_OWNER, PermissionType.OWNER, repositoryType);
TestData.createNotAdminUser(USER_OTHER, USER_PASS); TestData.createNotAdminUser(USER_OTHER, USER_PASS);
createdPermissions = 3; createdPermissions = asList(USER_READ, USER_WRITE, USER_OWNER);
} }
@Test @Test
@@ -131,8 +134,8 @@ public class PermissionsITCase {
@Test @Test
public void ownerShouldSeePermissions() { public void ownerShouldSeePermissions() {
List<Object> userPermissions = TestData.getUserPermissions(USER_OWNER, USER_PASS, repositoryType); List<Map> userPermissions = TestData.getUserPermissions(USER_OWNER, USER_PASS, repositoryType);
assertEquals(userPermissions.size(), createdPermissions); Assertions.assertThat(userPermissions).extracting(e -> e.get("name")).containsAll(createdPermissions);
} }
@Test @Test

View File

@@ -99,10 +99,10 @@ public class TestData {
; ;
} }
public static List<Object> getUserPermissions(String username, String password, String repositoryType) { public static List<Map> getUserPermissions(String username, String password, String repositoryType) {
return callUserPermissions(username, password, repositoryType, HttpStatus.SC_OK) return callUserPermissions(username, password, repositoryType, HttpStatus.SC_OK)
.extract() .extract()
.body().jsonPath().getList("_embedded.permissions"); .body().jsonPath().<Map>getList("_embedded.permissions");
} }
public static ValidatableResponse callUserPermissions(String username, String password, String repositoryType, int expectedStatusCode) { public static ValidatableResponse callUserPermissions(String username, String password, String repositoryType, int expectedStatusCode) {

View File

@@ -115,12 +115,6 @@
<artifactId>smp-maven-plugin</artifactId> <artifactId>smp-maven-plugin</artifactId>
<version>1.0.0-alpha-3</version> <version>1.0.0-alpha-3</version>
<extensions>true</extensions> <extensions>true</extensions>
<configuration>
<links>
<link>@scm-manager/ui-types</link>
<link>@scm-manager/ui-components</link>
</links>
</configuration>
</plugin> </plugin>
<plugin> <plugin>

View File

@@ -52,6 +52,10 @@
<extensions>true</extensions> <extensions>true</extensions>
<configuration> <configuration>
<corePlugin>true</corePlugin> <corePlugin>true</corePlugin>
<links>
<link>@scm-manager/ui-types</link>
<link>@scm-manager/ui-components</link>
</links>
</configuration> </configuration>
</plugin> </plugin>

View File

@@ -59,6 +59,10 @@
<extensions>true</extensions> <extensions>true</extensions>
<configuration> <configuration>
<corePlugin>true</corePlugin> <corePlugin>true</corePlugin>
<links>
<link>@scm-manager/ui-types</link>
<link>@scm-manager/ui-components</link>
</links>
</configuration> </configuration>
</plugin> </plugin>

View File

@@ -45,6 +45,10 @@
<artifactId>smp-maven-plugin</artifactId> <artifactId>smp-maven-plugin</artifactId>
<configuration> <configuration>
<corePlugin>true</corePlugin> <corePlugin>true</corePlugin>
<links>
<link>@scm-manager/ui-types</link>
<link>@scm-manager/ui-components</link>
</links>
</configuration> </configuration>
</plugin> </plugin>

View File

@@ -1,13 +1,16 @@
{ {
"name": "scm-ui-components", "name": "scm-ui-components",
"version": "0.0.3",
"description": "Lerna root for SCM-Manager UI Components", "description": "Lerna root for SCM-Manager UI Components",
"private": true, "private": true,
"scripts": { "scripts": {
"bootstrap": "lerna bootstrap", "bootstrap": "lerna bootstrap",
"link": "lerna exec -- yarn link", "link": "lerna exec -- yarn link",
"unlink": "lerna exec --no-bail -- yarn unlink || true" "unlink": "lerna exec --no-bail -- yarn unlink || true",
"deploy": "node ./scripts/publish.js"
}, },
"devDependencies": { "devDependencies": {
"lerna": "^3.2.1" "lerna": "^3.4.3",
"xml2js": "^0.4.19"
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@scm-manager/ui-components", "name": "@scm-manager/ui-components",
"version": "0.0.1", "version": "2.0.0-SNAPSHOT",
"description": "UI Components for SCM-Manager and its plugins", "description": "UI Components for SCM-Manager and its plugins",
"main": "src/index.js", "main": "src/index.js",
"files": [ "files": [
@@ -26,7 +26,7 @@
}, },
"dependencies": { "dependencies": {
"@scm-manager/ui-extensions": "^0.1.1", "@scm-manager/ui-extensions": "^0.1.1",
"@scm-manager/ui-types": "0.0.1", "@scm-manager/ui-types": "2.0.0-SNAPSHOT",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"moment": "^2.22.2", "moment": "^2.22.2",
"react": "^16.5.2", "react": "^16.5.2",

View File

@@ -1,39 +1,33 @@
//@flow //@flow
import React from "react"; import React from "react";
import injectSheet from "react-jss"; import injectSheet from "react-jss";
import classNames from "classnames"; import Tooltip from './Tooltip';
import HelpIcon from './HelpIcon';
const styles = { const styles = {
img: { tooltip: {
display: "block" display: "inline-block",
}, paddingLeft: "3px"
q: {
float: "left",
paddingLeft: "3px",
float: "right"
} }
}; };
type Props = { type Props = {
message: string, message: string,
classes: any classes: any
}; }
class Help extends React.Component<Props> { class Help extends React.Component<Props> {
render() { render() {
const { message, classes } = this.props; const { message, classes } = this.props;
const multiline = message.length > 60 ? "is-tooltip-multiline" : "";
return ( return (
<div <Tooltip className={classes.tooltip} message={message}>
className={classNames("tooltip is-tooltip-right", multiline, classes.q)} <HelpIcon />
data-tooltip={message} </Tooltip>
>
<i
className={classNames("fa fa-question has-text-info", classes.img)}
/>
</div>
); );
} }
} }
export default injectSheet(styles)(Help); export default injectSheet(styles)(Help);

View File

@@ -0,0 +1,14 @@
//@flow
import React from "react";
import classNames from "classnames";
type Props = {
};
class HelpIcon extends React.Component<Props> {
render() {
return <i className={classNames("fa fa-question has-text-info")} />
}
}
export default HelpIcon;

View File

@@ -1,46 +0,0 @@
//@flow
import React from "react";
import { Help } from "./index";
type Props = {
label: string,
helpText?: string
};
class LabelWithHelpIcon extends React.Component<Props> {
renderLabel = () => {
const label = this.props.label;
if (label) {
return <label className="label">{label}</label>;
}
return "";
};
renderHelp = () => {
const helpText = this.props.helpText;
if (helpText) {
return (
<div className="control columns is-vcentered">
<Help message={helpText} />
</div>
);
} else return null;
};
renderLabelWithHelpIcon = () => {
if (this.props.label) {
return (
<div className="field is-grouped">
<div className="control">{this.renderLabel()}</div>
{this.renderHelp()}
</div>
);
} else return null;
};
render() {
return this.renderLabelWithHelpIcon();
}
}
export default LabelWithHelpIcon;

View File

@@ -1,10 +1,14 @@
//@flow //@flow
import React from "react"; import React from "react";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import injectSheet from "react-jss"; import injectSheet from "react-jss";
import Image from "./Image"; import Image from "./Image";
const styles = { const styles = {
minHeightContainer: {
minHeight: "256px"
},
wrapper: { wrapper: {
position: "relative" position: "relative"
}, },
@@ -34,6 +38,7 @@ class Loading extends React.Component<Props> {
render() { render() {
const { message, t, classes } = this.props; const { message, t, classes } = this.props;
return ( return (
<div className={classes.minHeightContainer}>
<div className={classes.wrapper}> <div className={classes.wrapper}>
<div className={classes.loading}> <div className={classes.loading}>
<Image <Image
@@ -44,6 +49,7 @@ class Loading extends React.Component<Props> {
<p className="has-text-centered">{message}</p> <p className="has-text-centered">{message}</p>
</div> </div>
</div> </div>
</div>
); );
} }
} }

View File

@@ -0,0 +1,26 @@
//@flow
import * as React from "react";
import classNames from "classnames";
type Props = {
message: string,
className: string,
children: React.Node
};
class Tooltip extends React.Component<Props> {
render() {
const { className, message, children } = this.props;
const multiline = message.length > 60 ? "is-tooltip-multiline" : "";
return (
<div
className={classNames("tooltip", "is-tooltip-right", multiline, className)}
data-tooltip={message}
>
{children}
</div>
);
}
}
export default Tooltip;

View File

@@ -10,7 +10,9 @@ type Props = {
disabled?: boolean, disabled?: boolean,
helpText?: string helpText?: string
}; };
class Checkbox extends React.Component<Props> { class Checkbox extends React.Component<Props> {
onCheckboxChange = (event: SyntheticInputEvent<HTMLInputElement>) => { onCheckboxChange = (event: SyntheticInputEvent<HTMLInputElement>) => {
if (this.props.onChange) { if (this.props.onChange) {
this.props.onChange(event.target.checked, this.props.name); this.props.onChange(event.target.checked, this.props.name);
@@ -20,12 +22,8 @@ class Checkbox extends React.Component<Props> {
renderHelp = () => { renderHelp = () => {
const helpText = this.props.helpText; const helpText = this.props.helpText;
if (helpText) { if (helpText) {
return ( return <Help message={helpText} />;
<div className="control columns is-vcentered"> }
<Help message={helpText} />
</div>
);
} else return null;
}; };
render() { render() {
@@ -39,10 +37,11 @@ class Checkbox extends React.Component<Props> {
onChange={this.onCheckboxChange} onChange={this.onCheckboxChange}
disabled={this.props.disabled} disabled={this.props.disabled}
/> />
{" "}
{this.props.label} {this.props.label}
{this.renderHelp()}
</label> </label>
</div> </div>
{this.renderHelp()}
</div> </div>
); );
} }

View File

@@ -1,7 +1,7 @@
//@flow //@flow
import React from "react"; import React from "react";
import classNames from "classnames"; import classNames from "classnames";
import { LabelWithHelpIcon } from "../index"; import LabelWithHelpIcon from "./LabelWithHelpIcon";
type Props = { type Props = {
label?: string, label?: string,

View File

@@ -0,0 +1,37 @@
//@flow
import React from "react";
import Help from '../Help';
type Props = {
label?: string,
helpText?: string
};
class LabelWithHelpIcon extends React.Component<Props> {
renderHelp() {
const { helpText } = this.props;
if (helpText) {
return (
<Help message={helpText} />
);
}
}
render() {
const {label } = this.props;
if (label) {
const help = this.renderHelp();
return (
<label className="label">
{label} { help }
</label>
);
}
return "";
}
}
export default LabelWithHelpIcon;

View File

@@ -1,7 +1,7 @@
//@flow //@flow
import React from "react"; import React from "react";
import classNames from "classnames"; import classNames from "classnames";
import { LabelWithHelpIcon } from "../index"; import LabelWithHelpIcon from "./LabelWithHelpIcon";
export type SelectItem = { export type SelectItem = {
value: string, value: string,

View File

@@ -1,6 +1,6 @@
//@flow //@flow
import React from "react"; import React from "react";
import { LabelWithHelpIcon } from "../index"; import LabelWithHelpIcon from "./LabelWithHelpIcon";
export type SelectItem = { export type SelectItem = {
value: string, value: string,
@@ -8,10 +8,11 @@ export type SelectItem = {
}; };
type Props = { type Props = {
name?: string,
label?: string, label?: string,
placeholder?: SelectItem[], placeholder?: SelectItem[],
value?: string, value?: string,
onChange: string => void, onChange: (value: string, name?: string) => void,
helpText?: string helpText?: string
}; };
@@ -19,7 +20,7 @@ class Textarea extends React.Component<Props> {
field: ?HTMLTextAreaElement; field: ?HTMLTextAreaElement;
handleInput = (event: SyntheticInputEvent<HTMLTextAreaElement>) => { handleInput = (event: SyntheticInputEvent<HTMLTextAreaElement>) => {
this.props.onChange(event.target.value); this.props.onChange(event.target.value, this.props.name);
}; };
render() { render() {

View File

@@ -5,4 +5,5 @@ export { default as Checkbox } from "./Checkbox.js";
export { default as InputField } from "./InputField.js"; export { default as InputField } from "./InputField.js";
export { default as Select } from "./Select.js"; export { default as Select } from "./Select.js";
export { default as Textarea } from "./Textarea.js"; export { default as Textarea } from "./Textarea.js";
export { default as LabelWithHelpIcon } from "./LabelWithHelpIcon";

View File

@@ -17,8 +17,9 @@ export { default as Notification } from "./Notification.js";
export { default as Paginator } from "./Paginator.js"; export { default as Paginator } from "./Paginator.js";
export { default as LinkPaginator } from "./LinkPaginator.js"; export { default as LinkPaginator } from "./LinkPaginator.js";
export { default as ProtectedRoute } from "./ProtectedRoute.js"; export { default as ProtectedRoute } from "./ProtectedRoute.js";
export { default as Help } from "./Help.js"; export { default as Help } from "./Help";
export { default as LabelWithHelpIcon } from "./LabelWithHelpIcon.js"; export { default as HelpIcon } from "./HelpIcon";
export { default as Tooltip } from "./Tooltip";
export { getPageFromMatch } from "./urls"; export { getPageFromMatch } from "./urls";
export { apiClient, NOT_FOUND_ERROR, UNAUTHORIZED_ERROR } from "./apiclient.js"; export { apiClient, NOT_FOUND_ERROR, UNAUTHORIZED_ERROR } from "./apiclient.js";

View File

@@ -1,6 +1,6 @@
{ {
"name": "@scm-manager/ui-types", "name": "@scm-manager/ui-types",
"version": "0.0.1", "version": "2.0.0-SNAPSHOT",
"description": "Flow types for SCM-Manager related Objects", "description": "Flow types for SCM-Manager related Objects",
"main": "src/index.js", "main": "src/index.js",
"files": [ "files": [
@@ -8,7 +8,7 @@
], ],
"repository": "https://bitbucket.org/sdorra/scm-manager", "repository": "https://bitbucket.org/sdorra/scm-manager",
"author": "Sebastian Sdorra <sebastian.sdorra@cloudogu.com>", "author": "Sebastian Sdorra <sebastian.sdorra@cloudogu.com>",
"license" : "BSD-3-Clause", "license": "BSD-3-Clause",
"scripts": { "scripts": {
"lint": "ui-bunder lint", "lint": "ui-bunder lint",
"check": "flow check" "check": "flow check"
@@ -21,8 +21,14 @@
[ [
"babelify", "babelify",
{ {
"plugins": ["@babel/plugin-proposal-class-properties"], "plugins": [
"presets": ["@babel/preset-env", "@babel/preset-flow", "@babel/preset-react"] "@babel/plugin-proposal-class-properties"
],
"presets": [
"@babel/preset-env",
"@babel/preset-flow",
"@babel/preset-react"
]
} }
] ]
] ]

View File

@@ -1,52 +0,0 @@
Arguments:
/usr/bin/node /home/ssdorra/.yarn/bin/yarn.js add --dev ui-bundler
PATH:
/home/ssdorra/.yarn/bin:/usr/local/go/bin:/home/ssdorra/Projects/go/bin:/home/ssdorra/.local/bin:/usr/local/google-cloud-sdk/bin:/usr/local/go/bin:/home/ssdorra/.sdkman/candidates/maven/current/bin:/home/ssdorra/.sdkman/candidates/groovy/current/bin:/home/ssdorra/bin:/home/ssdorra/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/usr/lib/jvm/java-8-oracle/bin:/usr/lib/jvm/java-8-oracle/db/bin:/usr/lib/jvm/java-8-oracle/jre/bin:/home/ssdorra/Projects/go/bin
Yarn version:
1.9.2
Node version:
8.11.4
Platform:
linux x64
Trace:
Error: https://registry.yarnpkg.com/ui-bundler: Not found
at Request.params.callback [as _callback] (/home/ssdorra/.yarn/lib/cli.js:64150:18)
at Request.self.callback (/home/ssdorra/.yarn/lib/cli.js:137416:22)
at emitTwo (events.js:126:13)
at Request.emit (events.js:214:7)
at Request.<anonymous> (/home/ssdorra/.yarn/lib/cli.js:138388:10)
at emitOne (events.js:116:13)
at Request.emit (events.js:211:7)
at IncomingMessage.<anonymous> (/home/ssdorra/.yarn/lib/cli.js:138310:12)
at Object.onceWrapper (events.js:313:30)
at emitNone (events.js:111:20)
npm manifest:
{
"name": "@scm-manager/ui-types",
"version": "0.0.1",
"description": "Flow types for SCM-Manager related Objects",
"main": "src/index.js",
"files": [
"src"
],
"repository": "https://bitbucket.org/sdorra/scm-manager",
"author": "Sebastian Sdorra <sebastian.sdorra@cloudogu.com>",
"license": "MIT",
"scripts": {
"check": "flow check"
},
"devDependencies": {}
}
yarn manifest:
No manifest
Lockfile:
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

View File

@@ -68,6 +68,16 @@
<script>link</script> <script>link</script>
</configuration> </configuration>
</execution> </execution>
<execution>
<id>deploy</id>
<phase>deploy</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<script>deploy</script>
</configuration>
</execution>
</executions> </executions>
</plugin> </plugin>

View File

@@ -0,0 +1,140 @@
const fs = require("fs");
const path = require("path");
const {spawn} = require("child_process");
const parseString = require('xml2js').parseString;
function isSnapshot(version) {
return version.indexOf("SNAPSHOT") > 0;
}
function createSnapshotVersion(version) {
const date = new Date();
const year = date.getFullYear();
const month = date.getMonth().toString().padStart(2, "0");
const day = date.getDate().toString().padStart(2, "0");
const hours = date.getHours().toString().padStart(2, "0");
const minutes = date.getMinutes().toString().padStart(2, "0");
const seconds = date.getSeconds().toString().padStart(2, "0");
return version.replace("SNAPSHOT", `${year}${month}${day}-${hours}${minutes}${seconds}`);
}
function createVersionForPublishing(version) {
if (isSnapshot(version)) {
return createSnapshotVersion(version);
}
return version;
}
function publishPackages(version) {
console.log(`publish ${version} of all packages`);
return new Promise((resolve, reject) => {
const lerna = spawn("lerna", [
"exec", "--", "yarn", "publish", "--new-version", version, "--access", "public"
], {
stdio: [
process.stdin,
process.stdout,
process.stderr
]
});
lerna.on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error("publishing of packages failed with status code: " + code));
}
});
});
}
function setVersion(package, packages, newVersion) {
return new Promise((resolve, reject) => {
const packageJsonPath = path.join(package, "package.json");
fs.readFile(packageJsonPath, "utf-8" , (err, content) => {
if (err) {
reject(err);
} else {
const packageJson = JSON.parse(content);
packageJson.version = newVersion;
for (let dep in packageJson.dependencies) {
if (packages.indexOf(dep) >= 0) {
packageJson.dependencies[ dep ] = newVersion;
}
}
fs.writeFile( packageJsonPath, JSON.stringify(packageJson, null, 2), (err) => {
if (err) {
reject(err)
} else {
console.log("modified", packageJsonPath);
resolve();
}
});
}
});
});
}
function setVersions(newVersion) {
console.log("set versions of packages to", newVersion);
return new Promise((resolve, reject) => {
fs.readdir("packages", (err, packages) => {
if ( err ) {
reject(err);
} else {
const actions = [];
const packagesWithOrg = packages.map((name) => `@scm-manager/${name}`);
for (let pkg of packages) {
const action = setVersion(path.join("packages", pkg), packagesWithOrg, newVersion);
actions.push(action);
}
resolve(Promise.all(actions));
}
});
});
}
function getVersion() {
return new Promise((resolve, reject) => {
fs.readFile("pom.xml", "utf8", (err, xml) => {
if (err) {
reject(err);
} else {
parseString(xml, function (err, json) {
if (err) {
reject(err)
} else {
const project = json.project;
let version = project.version;
if (!version) {
version = project.parent.version;
}
version = version[0];
resolve(version)
}
});
}
});
});
}
getVersion()
.then(version => {
const publishVersion = createVersionForPublishing(version);
return setVersions(publishVersion)
.then(() => publishPackages(publishVersion))
.then(() => setVersions(version));
})
.catch((err) => {
throw err;
});

File diff suppressed because it is too large Load Diff

View File

@@ -124,7 +124,7 @@ class RepositoryForm extends React.Component<Props, State> {
const { repositoryTypes, t } = this.props; const { repositoryTypes, t } = this.props;
const repository = this.state.repository; const repository = this.state.repository;
return ( return (
<div> <>
<InputField <InputField
label={t("repository.name")} label={t("repository.name")}
onChange={this.handleNameChange} onChange={this.handleNameChange}
@@ -140,7 +140,7 @@ class RepositoryForm extends React.Component<Props, State> {
options={this.createSelectOptions(repositoryTypes)} options={this.createSelectOptions(repositoryTypes)}
helpText={t("help.typeHelpText")} helpText={t("help.typeHelpText")}
/> />
</div> </>
); );
} }

View File

@@ -35,6 +35,7 @@ import PermissionsNavLink from "../components/PermissionsNavLink";
import Sources from "../sources/containers/Sources"; import Sources from "../sources/containers/Sources";
import RepositoryNavLink from "../components/RepositoryNavLink"; import RepositoryNavLink from "../components/RepositoryNavLink";
import { getRepositoriesLink } from "../../modules/indexResource"; import { getRepositoriesLink } from "../../modules/indexResource";
import {ExtensionPoint} from '@scm-manager/ui-extensions';
type Props = { type Props = {
namespace: string, namespace: string,
@@ -104,6 +105,12 @@ class RepositoryRoot extends React.Component<Props> {
} }
const url = this.matchedUrl(); const url = this.matchedUrl();
const extensionProps = {
repository,
url
};
return ( return (
<Page title={repository.namespace + "/" + repository.name}> <Page title={repository.namespace + "/" + repository.name}>
<div className="columns"> <div className="columns">
@@ -165,6 +172,10 @@ class RepositoryRoot extends React.Component<Props> {
/> />
)} )}
/> />
<ExtensionPoint name="repository.route"
props={extensionProps}
renderAll={true}
/>
</Switch> </Switch>
</div> </div>
<div className="column"> <div className="column">
@@ -186,11 +197,15 @@ class RepositoryRoot extends React.Component<Props> {
label={t("repository-root.sources")} label={t("repository-root.sources")}
activeOnlyWhenExact={false} activeOnlyWhenExact={false}
/> />
<EditNavLink repository={repository} editUrl={`${url}/edit`} /> <ExtensionPoint name="repository.navigation"
props={extensionProps}
renderAll={true}
/>
<PermissionsNavLink <PermissionsNavLink
permissionUrl={`${url}/permissions`} permissionUrl={`${url}/permissions`}
repository={repository} repository={repository}
/> />
<EditNavLink repository={repository} editUrl={`${url}/edit`} />
</Section> </Section>
<Section label={t("repository-root.actions-label")}> <Section label={t("repository-root.actions-label")}>
<DeleteNavAction repository={repository} delete={this.delete} /> <DeleteNavAction repository={repository} delete={this.delete} />

View File

@@ -79,7 +79,7 @@ public class PermissionRootResource {
Repository repository = load(namespace, name); Repository repository = load(namespace, name);
RepositoryPermissions.permissionWrite(repository).check(); RepositoryPermissions.permissionWrite(repository).check();
checkPermissionAlreadyExists(permission, repository); checkPermissionAlreadyExists(permission, repository);
repository.getPermissions().add(dtoToModelMapper.map(permission)); repository.addPermission(dtoToModelMapper.map(permission));
manager.modify(repository); manager.modify(repository);
String urlPermissionName = modelToDtoMapper.getUrlPermissionName(permission); String urlPermissionName = modelToDtoMapper.getUrlPermissionName(permission);
return Response.created(URI.create(resourceLinks.permission().self(namespace, name, urlPermissionName))).build(); return Response.created(URI.create(resourceLinks.permission().self(namespace, name, urlPermissionName))).build();
@@ -209,7 +209,7 @@ public class PermissionRootResource {
.stream() .stream()
.filter(filterPermission(permissionName)) .filter(filterPermission(permissionName))
.findFirst() .findFirst()
.ifPresent(p -> repository.getPermissions().remove(p)) .ifPresent(repository::removePermission)
; ;
manager.modify(repository); manager.modify(repository);
log.info("the permission with name: {} is updated.", permissionName); log.info("the permission with name: {} is updated.", permissionName);

View File

@@ -5,12 +5,15 @@ import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
import com.webcohesion.enunciate.metadata.rs.ResponseHeaders; import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint; import com.webcohesion.enunciate.metadata.rs.TypeHint;
import org.apache.shiro.SecurityUtils;
import sonia.scm.repository.Permission;
import sonia.scm.repository.PermissionType;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.RepositoryManager;
import sonia.scm.user.User;
import sonia.scm.web.VndMediaType; import sonia.scm.web.VndMediaType;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named;
import javax.validation.Valid; import javax.validation.Valid;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue; import javax.ws.rs.DefaultValue;
@@ -21,6 +24,8 @@ import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam; import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import static java.util.Collections.singletonList;
public class RepositoryCollectionResource { public class RepositoryCollectionResource {
private static final int DEFAULT_PAGE_SIZE = 10; private static final int DEFAULT_PAGE_SIZE = 10;
@@ -89,7 +94,17 @@ public class RepositoryCollectionResource {
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created repository")) @ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created repository"))
public Response create(@Valid RepositoryDto repository) { public Response create(@Valid RepositoryDto repository) {
return adapter.create(repository, return adapter.create(repository,
() -> dtoToRepositoryMapper.map(repository, null), () -> createModelObjectFromDto(repository),
r -> resourceLinks.repository().self(r.getNamespace(), r.getName())); r -> resourceLinks.repository().self(r.getNamespace(), r.getName()));
} }
private Repository createModelObjectFromDto(@Valid RepositoryDto repositoryDto) {
Repository repository = dtoToRepositoryMapper.map(repositoryDto, null);
repository.setPermissions(singletonList(new Permission(currentUser(), PermissionType.OWNER)));
return repository;
}
private String currentUser() {
return SecurityUtils.getSubject().getPrincipals().oneByType(User.class).getName();
}
} }

View File

@@ -167,7 +167,7 @@ public class AuthorizationChangedEventProducer {
private boolean isAuthorizationDataModified(Repository repository, Repository beforeModification) { private boolean isAuthorizationDataModified(Repository repository, Repository beforeModification) {
return repository.isArchived() != beforeModification.isArchived() return repository.isArchived() != beforeModification.isArchived()
|| repository.isPublicReadable() != beforeModification.isPublicReadable() || repository.isPublicReadable() != beforeModification.isPublicReadable()
|| ! repository.getPermissions().equals(beforeModification.getPermissions()); || !(repository.getPermissions().containsAll(beforeModification.getPermissions()) && beforeModification.getPermissions().containsAll(repository.getPermissions()));
} }
private void fireEventForEveryUser() { private void fireEventForEveryUser() {

View File

@@ -54,13 +54,14 @@ import sonia.scm.cache.CacheManager;
import sonia.scm.group.GroupNames; import sonia.scm.group.GroupNames;
import sonia.scm.group.GroupPermissions; import sonia.scm.group.GroupPermissions;
import sonia.scm.plugin.Extension; import sonia.scm.plugin.Extension;
import sonia.scm.repository.Permission;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryDAO; import sonia.scm.repository.RepositoryDAO;
import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.user.User; import sonia.scm.user.User;
import sonia.scm.user.UserPermissions; import sonia.scm.user.UserPermissions;
import sonia.scm.util.Util; import sonia.scm.util.Util;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@@ -198,7 +199,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
private void collectRepositoryPermissions(Builder<String> builder, private void collectRepositoryPermissions(Builder<String> builder,
Repository repository, User user, GroupNames groups) Repository repository, User user, GroupNames groups)
{ {
List<sonia.scm.repository.Permission> repositoryPermissions Collection<Permission> repositoryPermissions
= repository.getPermissions(); = repository.getPermissions();
if (Util.isNotEmpty(repositoryPermissions)) if (Util.isNotEmpty(repositoryPermissions))

View File

@@ -402,18 +402,17 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
} }
private Repository createUserWithRepository(String userPermission) { private Repository createUserWithRepository(String userPermission) {
Repository mockRepository = mock(Repository.class); Repository mockRepository = new Repository();
when(mockRepository.getId()).thenReturn(REPOSITORY_NAME); mockRepository.setId(REPOSITORY_NAME);
when(mockRepository.getNamespace()).thenReturn(REPOSITORY_NAMESPACE); mockRepository.setNamespace(REPOSITORY_NAMESPACE);
when(mockRepository.getName()).thenReturn(REPOSITORY_NAME); mockRepository.setName(REPOSITORY_NAME);
when(mockRepository.getNamespaceAndName()).thenReturn(new NamespaceAndName(REPOSITORY_NAMESPACE, REPOSITORY_NAME));
when(repositoryManager.get(any(NamespaceAndName.class))).thenReturn(mockRepository); when(repositoryManager.get(any(NamespaceAndName.class))).thenReturn(mockRepository);
when(subject.isPermitted(userPermission != null ? eq(userPermission) : any(String.class))).thenReturn(true); when(subject.isPermitted(userPermission != null ? eq(userPermission) : any(String.class))).thenReturn(true);
return mockRepository; return mockRepository;
} }
private void createUserWithRepositoryAndPermissions(ArrayList<Permission> permissions, String userPermission) { private void createUserWithRepositoryAndPermissions(ArrayList<Permission> permissions, String userPermission) {
when(createUserWithRepository(userPermission).getPermissions()).thenReturn(permissions); createUserWithRepository(userPermission).setPermissions(permissions);
} }
private Stream<DynamicTest> createDynamicTestsToAssertResponses(ExpectedRequest... expectedRequests) { private Stream<DynamicTest> createDynamicTestsToAssertResponses(ExpectedRequest... expectedRequests) {
@@ -421,10 +420,9 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
.map(entry -> dynamicTest("the endpoint " + entry.description + " should return the status code " + entry.expectedResponseStatus, () -> assertExpectedRequest(entry))); .map(entry -> dynamicTest("the endpoint " + entry.description + " should return the status code " + entry.expectedResponseStatus, () -> assertExpectedRequest(entry)));
} }
private MockHttpResponse assertExpectedRequest(ExpectedRequest entry) throws URISyntaxException { private void assertExpectedRequest(ExpectedRequest entry) throws URISyntaxException {
MockHttpResponse response = new MockHttpResponse(); MockHttpResponse response = new MockHttpResponse();
HttpRequest request = null; HttpRequest request = MockHttpRequest
request = MockHttpRequest
.create(entry.method, "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + entry.path) .create(entry.method, "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + entry.path)
.content(entry.content) .content(entry.content)
.contentType(VndMediaType.PERMISSION); .contentType(VndMediaType.PERMISSION);
@@ -436,7 +434,6 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
if (entry.responseValidator != null) { if (entry.responseValidator != null) {
entry.responseValidator.accept(response); entry.responseValidator.accept(response);
} }
return response;
} }
@ToString @ToString
@@ -470,12 +467,12 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
return this; return this;
} }
public ExpectedRequest expectedResponseStatus(int expectedResponseStatus) { ExpectedRequest expectedResponseStatus(int expectedResponseStatus) {
this.expectedResponseStatus = expectedResponseStatus; this.expectedResponseStatus = expectedResponseStatus;
return this; return this;
} }
public ExpectedRequest responseValidator(Consumer<MockHttpResponse> responseValidator) { ExpectedRequest responseValidator(Consumer<MockHttpResponse> responseValidator) {
this.responseValidator = responseValidator; this.responseValidator = responseValidator;
return this; return this;
} }

View File

@@ -4,6 +4,9 @@ import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware; import com.github.sdorra.shiro.SubjectAware;
import com.google.common.io.Resources; import com.google.common.io.Resources;
import com.google.inject.util.Providers; import com.google.inject.util.Providers;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.assertj.core.api.Assertions;
import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse; import org.jboss.resteasy.mock.MockHttpResponse;
@@ -22,6 +25,7 @@ import sonia.scm.repository.RepositoryIsNotArchivedException;
import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.user.User;
import sonia.scm.web.VndMediaType; import sonia.scm.web.VndMediaType;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@@ -37,6 +41,7 @@ import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT; import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
import static javax.servlet.http.HttpServletResponse.SC_OK; import static javax.servlet.http.HttpServletResponse.SC_OK;
import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED; import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -59,6 +64,8 @@ import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
) )
public class RepositoryRootResourceTest extends RepositoryTestBase { public class RepositoryRootResourceTest extends RepositoryTestBase {
private static final String REALM = "AdminRealm";
private Dispatcher dispatcher; private Dispatcher dispatcher;
@Rule @Rule
@@ -96,6 +103,13 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
when(serviceFactory.create(any(Repository.class))).thenReturn(service); when(serviceFactory.create(any(Repository.class))).thenReturn(service);
when(scmPathInfoStore.get()).thenReturn(uriInfo); when(scmPathInfoStore.get()).thenReturn(uriInfo);
when(uriInfo.getApiRestUri()).thenReturn(URI.create("/x/y")); when(uriInfo.getApiRestUri()).thenReturn(URI.create("/x/y"));
SimplePrincipalCollection trillian = new SimplePrincipalCollection("trillian", REALM);
trillian.add(new User("trillian"), REALM);
shiro.setSubject(
new Subject.Builder()
.principals(trillian)
.authenticated(true)
.buildSubject());
} }
@Test @Test
@@ -257,6 +271,34 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
verify(repositoryManager).create(any(Repository.class)); verify(repositoryManager).create(any(Repository.class));
} }
@Test
public void shouldSetCurrentUserAsOwner() throws Exception {
ArgumentCaptor<Repository> createCaptor = ArgumentCaptor.forClass(Repository.class);
when(repositoryManager.create(createCaptor.capture())).thenAnswer(invocation -> {
Repository repository = (Repository) invocation.getArguments()[0];
repository.setNamespace("otherspace");
return repository;
});
URL url = Resources.getResource("sonia/scm/api/v2/repository-test-update.json");
byte[] repositoryJson = Resources.toByteArray(url);
MockHttpRequest request = MockHttpRequest
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2)
.contentType(VndMediaType.REPOSITORY)
.content(repositoryJson);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
Assertions.assertThat(createCaptor.getValue().getPermissions())
.hasSize(1)
.allSatisfy(p -> {
assertThat(p.getName()).isEqualTo("trillian");
assertThat(p.getType()).isEqualTo(PermissionType.OWNER);
});
}
@Test @Test
public void shouldNotOverwriteExistingPermissionsOnUpdate() throws Exception { public void shouldNotOverwriteExistingPermissionsOnUpdate() throws Exception {
Repository existingRepository = mockRepository("space", "repo"); Repository existingRepository = mockRepository("space", "repo");

View File

@@ -184,7 +184,7 @@ private long calculateAverage(List<Long> times) {
private Repository createTestRepository(int number) { private Repository createTestRepository(int number) {
Repository repository = new Repository(keyGenerator.createKey(), REPOSITORY_TYPE, "namespace", "repo-" + number); Repository repository = new Repository(keyGenerator.createKey(), REPOSITORY_TYPE, "namespace", "repo-" + number);
repository.getPermissions().add(new Permission("trillian", PermissionType.READ)); repository.addPermission(new Permission("trillian", PermissionType.READ));
return repository; return repository;
} }