From 68302fef926e912b528f47b0453271c47200cba3 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Fri, 18 Jan 2019 09:42:47 +0100 Subject: [PATCH 001/112] repository navigation --- scm-ui/public/locales/en/repos.json | 12 +++++------- scm-ui/src/repos/components/PermissionsNavLink.js | 2 +- scm-ui/src/repos/containers/RepositoryRoot.js | 13 ++++--------- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index d6cdaa1d8d..a1b006a1da 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -19,13 +19,11 @@ "repository-root": { "error-title": "Error", "error-subtitle": "Unknown repository error", - "actions-label": "Actions", - "back-label": "Back", - "navigation-label": "Navigation", - "history": "Commits", - "information": "Information", - "permissions": "Permissions", - "sources": "Sources" + "navigationLabel": "Repository Navigation", + "historyNavLink": "Commits", + "informationNavLink": "Information", + "permissionsNavLink": "Permissions", + "sourcesNavLink": "Sources" }, "create": { "title": "Create Repository", diff --git a/scm-ui/src/repos/components/PermissionsNavLink.js b/scm-ui/src/repos/components/PermissionsNavLink.js index cb6d0e0723..937980fd3d 100644 --- a/scm-ui/src/repos/components/PermissionsNavLink.js +++ b/scm-ui/src/repos/components/PermissionsNavLink.js @@ -20,7 +20,7 @@ class PermissionsNavLink extends React.Component { } const { permissionUrl, t } = this.props; return ( - + ); } } diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index e73348babc..bdfa74d5d7 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -168,13 +168,13 @@ class RepositoryRoot extends React.Component {
-
- +
+ @@ -182,24 +182,19 @@ class RepositoryRoot extends React.Component { repository={repository} linkName="sources" to={`${url}/sources`} - label={t("repository-root.sources")} + label={t("repository-root.sourcesNavLink")} activeOnlyWhenExact={false} /> -
-
- - -
From af164d31f435f1ff8b54d589d84af9adc8f67fe5 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Fri, 18 Jan 2019 09:51:27 +0100 Subject: [PATCH 002/112] updated user navigation --- scm-ui/public/locales/en/users.json | 14 ++++---------- .../users/components/navLinks/EditUserNavLink.js | 2 +- .../components/navLinks/SetPasswordNavLink.js | 2 +- scm-ui/src/users/containers/SingleUser.js | 8 ++------ 4 files changed, 8 insertions(+), 18 deletions(-) diff --git a/scm-ui/public/locales/en/users.json b/scm-ui/public/locales/en/users.json index 2a9ee7b79d..e7a637bc2b 100644 --- a/scm-ui/public/locales/en/users.json +++ b/scm-ui/public/locales/en/users.json @@ -26,12 +26,6 @@ "cancel": "No" } }, - "edit-user-button": { - "label": "Edit" - }, - "set-password-button": { - "label": "Set password" - }, "user-form": { "submit": "Submit" }, @@ -42,10 +36,10 @@ "single-user": { "error-title": "Error", "error-subtitle": "Unknown user error", - "navigation-label": "Navigation", - "actions-label": "Actions", - "information-label": "Information", - "back-label": "Back" + "navigationLabel": "User Navigation", + "informationNavLink": "Information", + "editNavLink": "Edit", + "setPasswordNavLink": "Set password" }, "validation": { "mail-invalid": "This email is invalid", diff --git a/scm-ui/src/users/components/navLinks/EditUserNavLink.js b/scm-ui/src/users/components/navLinks/EditUserNavLink.js index 9999428212..3689b445d8 100644 --- a/scm-ui/src/users/components/navLinks/EditUserNavLink.js +++ b/scm-ui/src/users/components/navLinks/EditUserNavLink.js @@ -17,7 +17,7 @@ class EditUserNavLink extends React.Component { if (!this.isEditable()) { return null; } - return ; + return ; } isEditable = () => { diff --git a/scm-ui/src/users/components/navLinks/SetPasswordNavLink.js b/scm-ui/src/users/components/navLinks/SetPasswordNavLink.js index 43b7a4b5a4..e9f28d841e 100644 --- a/scm-ui/src/users/components/navLinks/SetPasswordNavLink.js +++ b/scm-ui/src/users/components/navLinks/SetPasswordNavLink.js @@ -17,7 +17,7 @@ class ChangePasswordNavLink extends React.Component { if (!this.hasPermissionToSetPassword()) { return null; } - return ; + return ; } hasPermissionToSetPassword = () => { diff --git a/scm-ui/src/users/containers/SingleUser.js b/scm-ui/src/users/containers/SingleUser.js index 5f20598962..c980585141 100644 --- a/scm-ui/src/users/containers/SingleUser.js +++ b/scm-ui/src/users/containers/SingleUser.js @@ -109,10 +109,10 @@ class SingleUser extends React.Component {
-
+
{ passwordUrl={`${url}/password`} />
-
- - -
From 3318e5291e67413cf205eac2183ee963a466ba32 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Fri, 18 Jan 2019 10:10:03 +0100 Subject: [PATCH 003/112] updated config navigation --- scm-ui/public/locales/en/config.json | 10 +- scm-ui/src/config/containers/Config.js | 173 ++++++++++--------- scm-ui/src/config/containers/GlobalConfig.js | 6 +- 3 files changed, 94 insertions(+), 95 deletions(-) diff --git a/scm-ui/public/locales/en/config.json b/scm-ui/public/locales/en/config.json index 1a33da8c8b..436b96cab8 100644 --- a/scm-ui/public/locales/en/config.json +++ b/scm-ui/public/locales/en/config.json @@ -1,12 +1,10 @@ { "config": { - "navigation-title": "Navigation" - }, - "global-config": { + "navigationLabel": "Configuration Navigation", + "globalConfigurationNavLink": "Global Configuration", "title": "Configuration", - "navigation-label": "Global Configuration", - "error-title": "Error", - "error-subtitle": "Unknown Config Error" + "errorTitle": "Error", + "errorSubtitle": "Unknown Config Error" }, "config-form": { "submit": "Submit", diff --git a/scm-ui/src/config/containers/Config.js b/scm-ui/src/config/containers/Config.js index 60dc9bb75f..04de525c95 100644 --- a/scm-ui/src/config/containers/Config.js +++ b/scm-ui/src/config/containers/Config.js @@ -1,86 +1,87 @@ -// @flow -import React from "react"; -import { translate } from "react-i18next"; -import { Route } from "react-router"; -import { ExtensionPoint } from "@scm-manager/ui-extensions"; - -import type { Links } from "@scm-manager/ui-types"; -import { Page, Navigation, NavLink, Section } from "@scm-manager/ui-components"; -import GlobalConfig from "./GlobalConfig"; -import type { History } from "history"; -import {connect} from "react-redux"; -import {compose} from "redux"; -import { getLinks } from "../../modules/indexResource"; - -type Props = { - links: Links, - - // context objects - t: string => string, - match: any, - history: History -}; - -class Config extends React.Component { - stripEndingSlash = (url: string) => { - if (url.endsWith("/")) { - return url.substring(0, url.length - 2); - } - return url; - }; - - matchedUrl = () => { - return this.stripEndingSlash(this.props.match.url); - }; - - render() { - const { links, t } = this.props; - - const url = this.matchedUrl(); - const extensionProps = { - links, - url - }; - - return ( - -
-
- - -
-
- -
- - -
-
-
-
-
- ); - } -} - -const mapStateToProps = (state: any) => { - const links = getLinks(state); - return { - links - }; -}; - -export default compose( - connect(mapStateToProps), - translate("config") -)(Config); - +// @flow +import React from "react"; +import { translate } from "react-i18next"; +import { Route } from "react-router"; +import { ExtensionPoint } from "@scm-manager/ui-extensions"; + +import type { Links } from "@scm-manager/ui-types"; +import { Page, Navigation, NavLink, Section } from "@scm-manager/ui-components"; +import GlobalConfig from "./GlobalConfig"; +import type { History } from "history"; +import { connect } from "react-redux"; +import { compose } from "redux"; +import { getLinks } from "../../modules/indexResource"; + +type Props = { + links: Links, + + // context objects + t: string => string, + match: any, + history: History +}; + +class Config extends React.Component { + stripEndingSlash = (url: string) => { + if (url.endsWith("/")) { + return url.substring(0, url.length - 2); + } + return url; + }; + + matchedUrl = () => { + return this.stripEndingSlash(this.props.match.url); + }; + + render() { + const { links, t } = this.props; + + const url = this.matchedUrl(); + const extensionProps = { + links, + url + }; + + return ( + +
+
+ + +
+
+ +
+ + +
+
+
+
+
+ ); + } +} + +const mapStateToProps = (state: any) => { + const links = getLinks(state); + return { + links + }; +}; + +export default compose( + connect(mapStateToProps), + translate("config") +)(Config); diff --git a/scm-ui/src/config/containers/GlobalConfig.js b/scm-ui/src/config/containers/GlobalConfig.js index 71be3fdd7f..eac8e27bee 100644 --- a/scm-ui/src/config/containers/GlobalConfig.js +++ b/scm-ui/src/config/containers/GlobalConfig.js @@ -78,8 +78,8 @@ class GlobalConfig extends React.Component { if (error) { return ( @@ -91,7 +91,7 @@ class GlobalConfig extends React.Component { return (
- + <Title title={t("config.title")} /> {this.renderConfigChangedNotification()} <ConfigForm submitForm={config => this.modifyConfig(config)} From f96a7b6ca509e81446f40a69ae7655bd776466a7 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Fri, 18 Jan 2019 10:16:45 +0100 Subject: [PATCH 004/112] updated group navigation --- scm-ui/public/locales/en/groups.json | 10 ++++------ scm-ui/src/groups/containers/SingleGroup.js | 16 ++++------------ 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/scm-ui/public/locales/en/groups.json b/scm-ui/public/locales/en/groups.json index f1ebb95e18..66075be273 100644 --- a/scm-ui/public/locales/en/groups.json +++ b/scm-ui/public/locales/en/groups.json @@ -12,12 +12,10 @@ "subtitle": "Create, read, update and delete groups" }, "single-group": { - "error-title": "Error", - "error-subtitle": "Unknown group error", - "navigation-label": "Navigation", - "actions-label": "Actions", - "information-label": "Information", - "back-label": "Back" + "navigationLabel": "Group Navigation", + "informationNavLink": "Information", + "errorTitle": "Error", + "errorSubtitle": "Unknown group error" }, "add-group": { "title": "Create Group", diff --git a/scm-ui/src/groups/containers/SingleGroup.js b/scm-ui/src/groups/containers/SingleGroup.js index 1dd4aa569f..ec62333f6f 100644 --- a/scm-ui/src/groups/containers/SingleGroup.js +++ b/scm-ui/src/groups/containers/SingleGroup.js @@ -75,8 +75,8 @@ class SingleGroup extends React.Component<Props> { if (error) { return ( <ErrorPage - title={t("single-group.error-title")} - subtitle={t("single-group.error-subtitle")} + title={t("single-group.errorTitle")} + subtitle={t("single-group.errorSubtitle")} error={error} /> ); @@ -105,20 +105,12 @@ class SingleGroup extends React.Component<Props> { </div> <div className="column"> <Navigation> - <Section label={t("single-group.navigation-label")}> + <Section label={t("single-group.navigationLabel")}> <NavLink to={`${url}`} - label={t("single-group.information-label")} + label={t("single-group.informationNavLink")} /> </Section> - <Section label={t("single-group.actions-label")}> - <DeleteGroupNavLink - group={group} - deleteGroup={this.deleteGroup} - /> - <EditGroupNavLink group={group} editUrl={`${url}/edit`} /> - <NavLink to="/groups" label={t("single-group.back-label")} /> - </Section> </Navigation> </div> </div> From 03813b40af165195d9f8bda08458f3a95c91fbc3 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Fri, 18 Jan 2019 10:25:35 +0100 Subject: [PATCH 005/112] updated profile navigation + some naming changes --- scm-ui/public/locales/en/commons.json | 7 +++---- scm-ui/public/locales/en/repos.json | 6 +++--- scm-ui/public/locales/en/users.json | 6 +++--- scm-ui/src/containers/Profile.js | 8 +++----- scm-ui/src/repos/containers/RepositoryRoot.js | 4 ++-- scm-ui/src/users/containers/SingleUser.js | 4 ++-- 6 files changed, 16 insertions(+), 19 deletions(-) diff --git a/scm-ui/public/locales/en/commons.json b/scm-ui/public/locales/en/commons.json index 3196f3a328..ca4a1c87bf 100644 --- a/scm-ui/public/locales/en/commons.json +++ b/scm-ui/public/locales/en/commons.json @@ -43,13 +43,12 @@ "previous": "Previous" }, "profile": { - "navigation-label": "Navigation", - "actions-label": "Actions", + "navigationLabel": "Profile Navigation", + "informationNavLink": "Information", + "changePasswordNavLink": "Change password", "username": "Username", "displayName": "Display Name", "mail": "E-Mail", - "information": "Information", - "change-password": "Change password", "error-title": "Error", "error-subtitle": "Cannot display profile", "error": "Error", diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index a1b006a1da..68f5b4a53b 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -17,13 +17,13 @@ "create-button": "Create" }, "repository-root": { - "error-title": "Error", - "error-subtitle": "Unknown repository error", "navigationLabel": "Repository Navigation", "historyNavLink": "Commits", "informationNavLink": "Information", "permissionsNavLink": "Permissions", - "sourcesNavLink": "Sources" + "sourcesNavLink": "Sources", + "errorTitle": "Error", + "errorSubtitle": "Unknown repository error" }, "create": { "title": "Create Repository", diff --git a/scm-ui/public/locales/en/users.json b/scm-ui/public/locales/en/users.json index e7a637bc2b..e388081504 100644 --- a/scm-ui/public/locales/en/users.json +++ b/scm-ui/public/locales/en/users.json @@ -34,12 +34,12 @@ "subtitle": "Create a new user" }, "single-user": { - "error-title": "Error", - "error-subtitle": "Unknown user error", "navigationLabel": "User Navigation", "informationNavLink": "Information", "editNavLink": "Edit", - "setPasswordNavLink": "Set password" + "setPasswordNavLink": "Set password", + "errorTitle": "Error", + "errorSubtitle": "Unknown user error" }, "validation": { "mail-invalid": "This email is invalid", diff --git a/scm-ui/src/containers/Profile.js b/scm-ui/src/containers/Profile.js index b40f5f3ee0..b28a8cd705 100644 --- a/scm-ui/src/containers/Profile.js +++ b/scm-ui/src/containers/Profile.js @@ -69,13 +69,11 @@ class Profile extends React.Component<Props, State> { </div> <div className="column"> <Navigation> - <Section label={t("profile.navigation-label")}> - <NavLink to={`${url}`} label={t("profile.information")} /> - </Section> - <Section label={t("profile.actions-label")}> + <Section label={t("profile.navigationLabel")}> + <NavLink to={`${url}`} label={t("profile.informationNavLink")} /> <NavLink to={`${url}/password`} - label={t("profile.change-password")} + label={t("profile.changePasswordNavLink")} /> </Section> </Navigation> diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index bdfa74d5d7..9fcae12c53 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -80,8 +80,8 @@ class RepositoryRoot extends React.Component<Props> { if (error) { return ( <ErrorPage - title={t("repository-root.error-title")} - subtitle={t("repository-root.error-subtitle")} + title={t("repository-root.errorTitle")} + subtitle={t("repository-root.errorSubtitle")} error={error} /> ); diff --git a/scm-ui/src/users/containers/SingleUser.js b/scm-ui/src/users/containers/SingleUser.js index c980585141..1662d213c5 100644 --- a/scm-ui/src/users/containers/SingleUser.js +++ b/scm-ui/src/users/containers/SingleUser.js @@ -80,8 +80,8 @@ class SingleUser extends React.Component<Props> { if (error) { return ( <ErrorPage - title={t("single-user.error-title")} - subtitle={t("single-user.error-subtitle")} + title={t("single-user.errorTitle")} + subtitle={t("single-user.errorSubtitle")} error={error} /> ); From 83321a195e63fb134ccfbfe0ccb2fe75f40b7d1b Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Fri, 18 Jan 2019 10:32:47 +0100 Subject: [PATCH 006/112] undo changes from switching from wrong branch --- .../src/repos/changesets/ChangesetRow.js | 19 +++++-------------- scm-ui/public/locales/en/commons.json | 2 +- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetRow.js b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetRow.js index d74e3631d1..ef9de9bfe5 100644 --- a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetRow.js +++ b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetRow.js @@ -3,16 +3,15 @@ import React from "react"; import type { Changeset, Repository, Tag } from "@scm-manager/ui-types"; import classNames from "classnames"; -import { Interpolate, translate } from "react-i18next"; +import {Interpolate, translate} from "react-i18next"; import ChangesetId from "./ChangesetId"; import injectSheet from "react-jss"; -import { DateFromNow } from "../.."; +import {DateFromNow} from "../.."; import ChangesetAuthor from "./ChangesetAuthor"; import ChangesetTag from "./ChangesetTag"; -import { parseDescription } from "./changesets"; -import { AvatarWrapper, AvatarImage } from "../../avatar"; -import { ExtensionPoint } from "@scm-manager/ui-extensions"; +import {parseDescription} from "./changesets"; +import {AvatarWrapper, AvatarImage} from "../../avatar"; const styles = { pointer: { @@ -65,15 +64,7 @@ class ChangesetRow extends React.Component<Props> { <div className={classNames("media-content", classes.withOverflow)}> <div className="content"> <p className="is-ellipsis-overflow"> - <strong> - <ExtensionPoint - name="changesets.changeset.description" - props={{ changeset, value: description.title }} - renderAll={true} - > - {description.title} - </ExtensionPoint> - </strong> + <strong>{description.title}</strong> <br /> <Interpolate i18nKey="changesets.changeset.summary" diff --git a/scm-ui/public/locales/en/commons.json b/scm-ui/public/locales/en/commons.json index ca4a1c87bf..3453ffc5ad 100644 --- a/scm-ui/public/locales/en/commons.json +++ b/scm-ui/public/locales/en/commons.json @@ -65,6 +65,6 @@ "passwordInvalid": "Password has to be between 6 and 32 characters", "passwordConfirmFailed": "Passwords have to be identical", "submit": "Submit", - "changedSuccessfully": "Pasword successfully changed" + "changedSuccessfully": "Password changed successfully" } } From 88afe18384b9c8d673fb9d3407f609cd3f0532c3 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Fri, 18 Jan 2019 11:41:41 +0100 Subject: [PATCH 007/112] implemented subnav and added settings for profile --- .../src/navigation/SubNavigation.js | 51 +++++++++++++++++++ .../ui-components/src/navigation/index.js | 1 + scm-ui/public/locales/en/commons.json | 1 + scm-ui/src/containers/Profile.js | 17 +++++-- 4 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 scm-ui-components/packages/ui-components/src/navigation/SubNavigation.js diff --git a/scm-ui-components/packages/ui-components/src/navigation/SubNavigation.js b/scm-ui-components/packages/ui-components/src/navigation/SubNavigation.js new file mode 100644 index 0000000000..b577955a34 --- /dev/null +++ b/scm-ui-components/packages/ui-components/src/navigation/SubNavigation.js @@ -0,0 +1,51 @@ +//@flow +import * as React from "react"; +import {Link, Route} from "react-router-dom"; + +type Props = { + to: string, + label: string, + activeOnlyWhenExact?: boolean, + activeWhenMatch?: (route: any) => boolean, + children?: React.Node +}; + +class SubNavigation extends React.Component<Props> { + static defaultProps = { + activeOnlyWhenExact: false + }; + + isActive(route: any) { + const { activeWhenMatch } = this.props; + return route.match || (activeWhenMatch && activeWhenMatch(route)); + } + + renderLink = (route: any) => { + const { to, label } = this.props; + + let children = null; + if(this.isActive(route)) { + children = ( + <ul>{this.props.children}</ul> + ); + } + + return ( + <li> + <Link className={this.isActive(route) ? "is-active" : ""} to={to}> + {label} + </Link> + {children} + </li> + ); + }; + + render() { + const { to, activeOnlyWhenExact } = this.props; + return ( + <Route path={to} exact={activeOnlyWhenExact} children={this.renderLink} /> + ); + } +} + +export default SubNavigation; diff --git a/scm-ui-components/packages/ui-components/src/navigation/index.js b/scm-ui-components/packages/ui-components/src/navigation/index.js index ca82073b56..b696f98328 100644 --- a/scm-ui-components/packages/ui-components/src/navigation/index.js +++ b/scm-ui-components/packages/ui-components/src/navigation/index.js @@ -3,6 +3,7 @@ export { default as NavAction } from "./NavAction.js"; export { default as NavLink } from "./NavLink.js"; export { default as Navigation } from "./Navigation.js"; +export { default as SubNavigation } from "./SubNavigation.js"; export { default as PrimaryNavigation } from "./PrimaryNavigation.js"; export { default as PrimaryNavigationLink } from "./PrimaryNavigationLink.js"; export { default as Section } from "./Section.js"; diff --git a/scm-ui/public/locales/en/commons.json b/scm-ui/public/locales/en/commons.json index 3453ffc5ad..a1a7ce7028 100644 --- a/scm-ui/public/locales/en/commons.json +++ b/scm-ui/public/locales/en/commons.json @@ -46,6 +46,7 @@ "navigationLabel": "Profile Navigation", "informationNavLink": "Information", "changePasswordNavLink": "Change password", + "settingsNavLink": "Settings", "username": "Username", "displayName": "Display Name", "mail": "E-Mail", diff --git a/scm-ui/src/containers/Profile.js b/scm-ui/src/containers/Profile.js index b28a8cd705..e603a565e8 100644 --- a/scm-ui/src/containers/Profile.js +++ b/scm-ui/src/containers/Profile.js @@ -12,6 +12,7 @@ import { ErrorPage, Page, Navigation, + SubNavigation, Section, NavLink } from "@scm-manager/ui-components"; @@ -63,18 +64,26 @@ class Profile extends React.Component<Props, State> { <div className="column is-three-quarters"> <Route path={url} exact render={() => <ProfileInfo me={me} />} /> <Route - path={`${url}/password`} + path={`${url}/settings/password`} render={() => <ChangeUserPassword me={me} />} /> </div> <div className="column"> <Navigation> <Section label={t("profile.navigationLabel")}> - <NavLink to={`${url}`} label={t("profile.informationNavLink")} /> <NavLink - to={`${url}/password`} - label={t("profile.changePasswordNavLink")} + to={`${url}`} + label={t("profile.informationNavLink")} /> + <SubNavigation + to={`${url}/settings/password`} + label={t("profile.settingsNavLink")} + > + <NavLink + to={`${url}/settings/password`} + label={t("profile.changePasswordNavLink")} + /> + </SubNavigation> </Section> </Navigation> </div> From 8f340ceab026776e095e52d79e95edb02e92acfc Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Fri, 18 Jan 2019 11:53:14 +0100 Subject: [PATCH 008/112] added settings for user --- scm-ui/public/locales/en/users.json | 1 + scm-ui/src/containers/Profile.js | 2 +- scm-ui/src/users/containers/SingleUser.js | 20 +++++++++++++------- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/scm-ui/public/locales/en/users.json b/scm-ui/public/locales/en/users.json index e388081504..cb08387ef2 100644 --- a/scm-ui/public/locales/en/users.json +++ b/scm-ui/public/locales/en/users.json @@ -36,6 +36,7 @@ "single-user": { "navigationLabel": "User Navigation", "informationNavLink": "Information", + "settingsNavLink": "Settings", "editNavLink": "Edit", "setPasswordNavLink": "Set password", "errorTitle": "Error", diff --git a/scm-ui/src/containers/Profile.js b/scm-ui/src/containers/Profile.js index e603a565e8..2ad78d3527 100644 --- a/scm-ui/src/containers/Profile.js +++ b/scm-ui/src/containers/Profile.js @@ -76,7 +76,7 @@ class Profile extends React.Component<Props, State> { label={t("profile.informationNavLink")} /> <SubNavigation - to={`${url}/settings/password`} + to={`${url}/settings`} label={t("profile.settingsNavLink")} > <NavLink diff --git a/scm-ui/src/users/containers/SingleUser.js b/scm-ui/src/users/containers/SingleUser.js index 1662d213c5..c8e3990f6d 100644 --- a/scm-ui/src/users/containers/SingleUser.js +++ b/scm-ui/src/users/containers/SingleUser.js @@ -5,6 +5,7 @@ import { Page, Loading, Navigation, + SubNavigation, Section, NavLink, ErrorPage @@ -99,11 +100,11 @@ class SingleUser extends React.Component<Props> { <div className="column is-three-quarters"> <Route path={url} exact component={() => <Details user={user} />} /> <Route - path={`${url}/edit`} + path={`${url}/settings/edit`} component={() => <EditUser user={user} />} /> <Route - path={`${url}/password`} + path={`${url}/settings/password`} component={() => <SetUserPassword user={user} />} /> </div> @@ -114,11 +115,16 @@ class SingleUser extends React.Component<Props> { to={`${url}`} label={t("single-user.informationNavLink")} /> - <EditUserNavLink user={user} editUrl={`${url}/edit`} /> - <SetPasswordNavLink - user={user} - passwordUrl={`${url}/password`} - /> + <SubNavigation + to={`${url}/settings`} + label={t("single-user.settingsNavLink")} + > + <EditUserNavLink user={user} editUrl={`${url}/settings/edit`} /> + <SetPasswordNavLink + user={user} + passwordUrl={`${url}/settings/password`} + /> + </SubNavigation> </Section> </Navigation> </div> From 71fc38dd1d6ae7d7915e5e8cb8ae458ccff34dfb Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Fri, 18 Jan 2019 13:42:11 +0100 Subject: [PATCH 009/112] first try on delete user --- scm-ui/public/locales/en/users.json | 3 ++- .../DeleteUserNavLink.js => DeleteUser.js} | 8 ++++---- ...eleteUserNavLink.test.js => DeleteUser.test.js} | 14 +++++++------- scm-ui/src/users/components/UserForm.js | 4 ++++ scm-ui/src/users/components/navLinks/index.js | 1 - scm-ui/src/users/containers/SingleUser.js | 1 - 6 files changed, 17 insertions(+), 14 deletions(-) rename scm-ui/src/users/components/{navLinks/DeleteUserNavLink.js => DeleteUser.js} (81%) rename scm-ui/src/users/components/{navLinks/DeleteUserNavLink.test.js => DeleteUser.test.js} (82%) diff --git a/scm-ui/public/locales/en/users.json b/scm-ui/public/locales/en/users.json index cb08387ef2..efcfab95b7 100644 --- a/scm-ui/public/locales/en/users.json +++ b/scm-ui/public/locales/en/users.json @@ -27,7 +27,8 @@ } }, "user-form": { - "submit": "Submit" + "submit": "Submit", + "deleteUser": "Delete User" }, "add-user": { "title": "Create User", diff --git a/scm-ui/src/users/components/navLinks/DeleteUserNavLink.js b/scm-ui/src/users/components/DeleteUser.js similarity index 81% rename from scm-ui/src/users/components/navLinks/DeleteUserNavLink.js rename to scm-ui/src/users/components/DeleteUser.js index 47fdae0f92..4037f5bed6 100644 --- a/scm-ui/src/users/components/navLinks/DeleteUserNavLink.js +++ b/scm-ui/src/users/components/DeleteUser.js @@ -2,7 +2,7 @@ import React from "react"; import { translate } from "react-i18next"; import type { User } from "@scm-manager/ui-types"; -import { NavAction, confirmAlert } from "@scm-manager/ui-components"; +import { DeleteButton, confirmAlert } from "@scm-manager/ui-components"; type Props = { user: User, @@ -11,7 +11,7 @@ type Props = { deleteUser: (user: User) => void }; -class DeleteUserNavLink extends React.Component<Props> { +class DeleteUser extends React.Component<Props> { static defaultProps = { confirmDialog: true }; @@ -49,8 +49,8 @@ class DeleteUserNavLink extends React.Component<Props> { if (!this.isDeletable()) { return null; } - return <NavAction label={t("delete-user-button.label")} action={action} />; + return <DeleteButton label={t("user-form.deleteUser")} action={action} />; } } -export default translate("users")(DeleteUserNavLink); +export default translate("users")(DeleteUser); diff --git a/scm-ui/src/users/components/navLinks/DeleteUserNavLink.test.js b/scm-ui/src/users/components/DeleteUser.test.js similarity index 82% rename from scm-ui/src/users/components/navLinks/DeleteUserNavLink.test.js rename to scm-ui/src/users/components/DeleteUser.test.js index 500235ab94..55c49c6648 100644 --- a/scm-ui/src/users/components/navLinks/DeleteUserNavLink.test.js +++ b/scm-ui/src/users/components/DeleteUser.test.js @@ -1,8 +1,8 @@ import React from "react"; import { mount, shallow } from "enzyme"; -import "../../../tests/enzyme"; +import "../../tests/enzyme"; import "../../../tests/i18n"; -import DeleteUserNavLink from "./DeleteUserNavLink"; +import DeleteUser from "../DeleteUser"; import { confirmAlert } from "@scm-manager/ui-components"; jest.mock("@scm-manager/ui-components", () => ({ @@ -10,14 +10,14 @@ jest.mock("@scm-manager/ui-components", () => ({ NavAction: require.requireActual("@scm-manager/ui-components").NavAction })); -describe("DeleteUserNavLink", () => { +describe("DeleteUser", () => { it("should render nothing, if the delete link is missing", () => { const user = { _links: {} }; const navLink = shallow( - <DeleteUserNavLink user={user} deleteUser={() => {}} /> + <DeleteUser user={user} deleteUser={() => {}} /> ); expect(navLink.text()).toBe(""); }); @@ -32,7 +32,7 @@ describe("DeleteUserNavLink", () => { }; const navLink = mount( - <DeleteUserNavLink user={user} deleteUser={() => {}} /> + <DeleteUser user={user} deleteUser={() => {}} /> ); expect(navLink.text()).not.toBe(""); }); @@ -47,7 +47,7 @@ describe("DeleteUserNavLink", () => { }; const navLink = mount( - <DeleteUserNavLink user={user} deleteUser={() => {}} /> + <DeleteUser user={user} deleteUser={() => {}} /> ); navLink.find("a").simulate("click"); @@ -69,7 +69,7 @@ describe("DeleteUserNavLink", () => { } const navLink = mount( - <DeleteUserNavLink + <DeleteUser user={user} confirmDialog={false} deleteUser={capture} diff --git a/scm-ui/src/users/components/UserForm.js b/scm-ui/src/users/components/UserForm.js index 2d3a519f15..dde5d9bf18 100644 --- a/scm-ui/src/users/components/UserForm.js +++ b/scm-ui/src/users/components/UserForm.js @@ -10,6 +10,7 @@ import { validation as validator } from "@scm-manager/ui-components"; import * as userValidator from "./userValidation"; +import DeleteUser from "./DeleteUser"; type Props = { submitForm: User => void, @@ -153,6 +154,9 @@ class UserForm extends React.Component<Props, State> { label={t("user-form.submit")} /> </div> + <div className="column"> + <DeleteUser user={user} /> + </div> </div> </form> ); diff --git a/scm-ui/src/users/components/navLinks/index.js b/scm-ui/src/users/components/navLinks/index.js index a6d8370c00..64f7536c4c 100644 --- a/scm-ui/src/users/components/navLinks/index.js +++ b/scm-ui/src/users/components/navLinks/index.js @@ -1,3 +1,2 @@ -export { default as DeleteUserNavLink } from "./DeleteUserNavLink"; export { default as EditUserNavLink } from "./EditUserNavLink"; export { default as SetPasswordNavLink } from "./SetPasswordNavLink"; diff --git a/scm-ui/src/users/containers/SingleUser.js b/scm-ui/src/users/containers/SingleUser.js index c8e3990f6d..6fb3ccfd37 100644 --- a/scm-ui/src/users/containers/SingleUser.js +++ b/scm-ui/src/users/containers/SingleUser.js @@ -26,7 +26,6 @@ import { } from "../modules/users"; import { - DeleteUserNavLink, EditUserNavLink, SetPasswordNavLink } from "./../components/navLinks"; From de381f767386348a9785f5cf302cbe2cb9fdf2c2 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Fri, 18 Jan 2019 13:42:48 +0100 Subject: [PATCH 010/112] changed for submenu styling --- scm-ui/styles/scm.scss | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/scm-ui/styles/scm.scss b/scm-ui/styles/scm.scss index f2a373ff09..b3764749a3 100644 --- a/scm-ui/styles/scm.scss +++ b/scm-ui/styles/scm.scss @@ -256,9 +256,6 @@ $fa-font-path: "webfonts"; border-radius: 0; color: #333; padding: 1rem; - border-top: 1px solid #eee; - border-left: 1px solid #eee; - border-right: 1px solid #eee; &.is-active { color: $blue; @@ -277,10 +274,27 @@ $fa-font-path: "webfonts"; } } } - > li:first-child > a { + li { + ul { + margin: 0; + border-top: 1px solid #eee; + + li { + border-right: none; + } + li:last-child { + border-bottom: none; + } + } + + border-top: 1px solid #eee; + border-left: 1px solid #eee; + border-right: 1px solid #eee; + } + li:first-child { border-top: none; } - li:last-child > a { + li:last-child { border-bottom: 1px solid #eee; } } From c6b43ec460f448a5da39f556240dc82d56b0d00c Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Fri, 18 Jan 2019 14:32:35 +0100 Subject: [PATCH 011/112] fixed submenu navlinks --- .../ui-components/src/navigation/SubNavigation.js | 8 +++++++- scm-ui/src/containers/Profile.js | 2 +- scm-ui/src/users/containers/SingleUser.js | 2 +- scm-ui/styles/scm.scss | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/navigation/SubNavigation.js b/scm-ui-components/packages/ui-components/src/navigation/SubNavigation.js index b577955a34..0eef244f59 100644 --- a/scm-ui-components/packages/ui-components/src/navigation/SubNavigation.js +++ b/scm-ui-components/packages/ui-components/src/navigation/SubNavigation.js @@ -42,8 +42,14 @@ class SubNavigation extends React.Component<Props> { render() { const { to, activeOnlyWhenExact } = this.props; + + // removes last part of url + let parents = to.split("/"); + parents.splice(-1,1); + let parent = parents.join("/"); + return ( - <Route path={to} exact={activeOnlyWhenExact} children={this.renderLink} /> + <Route path={parent} exact={activeOnlyWhenExact} children={this.renderLink} /> ); } } diff --git a/scm-ui/src/containers/Profile.js b/scm-ui/src/containers/Profile.js index 2ad78d3527..e603a565e8 100644 --- a/scm-ui/src/containers/Profile.js +++ b/scm-ui/src/containers/Profile.js @@ -76,7 +76,7 @@ class Profile extends React.Component<Props, State> { label={t("profile.informationNavLink")} /> <SubNavigation - to={`${url}/settings`} + to={`${url}/settings/password`} label={t("profile.settingsNavLink")} > <NavLink diff --git a/scm-ui/src/users/containers/SingleUser.js b/scm-ui/src/users/containers/SingleUser.js index 6fb3ccfd37..dfc07d5942 100644 --- a/scm-ui/src/users/containers/SingleUser.js +++ b/scm-ui/src/users/containers/SingleUser.js @@ -115,7 +115,7 @@ class SingleUser extends React.Component<Props> { label={t("single-user.informationNavLink")} /> <SubNavigation - to={`${url}/settings`} + to={`${url}/settings/edit`} label={t("single-user.settingsNavLink")} > <EditUserNavLink user={user} editUrl={`${url}/settings/edit`} /> diff --git a/scm-ui/styles/scm.scss b/scm-ui/styles/scm.scss index b3764749a3..df32e8eb7d 100644 --- a/scm-ui/styles/scm.scss +++ b/scm-ui/styles/scm.scss @@ -253,7 +253,6 @@ $fa-font-path: "webfonts"; } .menu-list { a { - border-radius: 0; color: #333; padding: 1rem; @@ -287,6 +286,7 @@ $fa-font-path: "webfonts"; } } + border-radius: 0; border-top: 1px solid #eee; border-left: 1px solid #eee; border-right: 1px solid #eee; From b1b38276ab50cf8c5e7fcf7f5a327fe9477272ca Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Fri, 18 Jan 2019 15:26:55 +0100 Subject: [PATCH 012/112] moved funcs from singleuser to deleteuser + renamed edit to general --- scm-ui/public/locales/en/users.json | 4 +- scm-ui/src/users/components/DeleteUser.js | 54 ++++++++++++++++++- .../src/users/components/DeleteUser.test.js | 2 +- scm-ui/src/users/containers/SingleUser.js | 32 +++-------- 4 files changed, 63 insertions(+), 29 deletions(-) diff --git a/scm-ui/public/locales/en/users.json b/scm-ui/public/locales/en/users.json index efcfab95b7..957fb8f12c 100644 --- a/scm-ui/public/locales/en/users.json +++ b/scm-ui/public/locales/en/users.json @@ -38,8 +38,8 @@ "navigationLabel": "User Navigation", "informationNavLink": "Information", "settingsNavLink": "Settings", - "editNavLink": "Edit", - "setPasswordNavLink": "Set password", + "editNavLink": "General", + "setPasswordNavLink": "Password", "errorTitle": "Error", "errorSubtitle": "Unknown user error" }, diff --git a/scm-ui/src/users/components/DeleteUser.js b/scm-ui/src/users/components/DeleteUser.js index 4037f5bed6..775f243ac6 100644 --- a/scm-ui/src/users/components/DeleteUser.js +++ b/scm-ui/src/users/components/DeleteUser.js @@ -3,15 +3,37 @@ import React from "react"; import { translate } from "react-i18next"; import type { User } from "@scm-manager/ui-types"; import { DeleteButton, confirmAlert } from "@scm-manager/ui-components"; +import {connect} from "react-redux"; +import { + deleteUser, fetchUserByName, + getDeleteUserFailure, + getUserByName, + isDeleteUserPending, +} from "../modules/users"; +import type {History} from "history"; type Props = { user: User, confirmDialog?: boolean, + + // dispatcher functions + fetchUserByName: (string, string) => void, + deleteUser: (user: User, callback?: () => void) => void, + + // context objects t: string => string, - deleteUser: (user: User) => void + history: History }; class DeleteUser extends React.Component<Props> { + userDeleted = () => { + this.props.history.push("/users"); + }; + + deleteUser = (user: User) => { + this.props.deleteUser(user, this.userDeleted); + }; + static defaultProps = { confirmDialog: true }; @@ -53,4 +75,34 @@ class DeleteUser extends React.Component<Props> { } } +/* +const mapStateToProps = (state, ownProps) => { + const name = ownProps.match.params.name; + const user = getUserByName(state, name); + const loading = isDeleteUserPending(state, name); + const error = getDeleteUserFailure(state, name); + return { + name, + user, + loading, + error + }; +}; + +const mapDispatchToProps = dispatch => { + return { + fetchUserByName: (link: string, name: string) => { + dispatch(fetchUserByName(link, name)); + }, + deleteUser: (user: User, callback?: () => void) => { + dispatch(deleteUser(user, callback)); + } + }; +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(translate("users")(DeleteUser)); +*/ export default translate("users")(DeleteUser); diff --git a/scm-ui/src/users/components/DeleteUser.test.js b/scm-ui/src/users/components/DeleteUser.test.js index 55c49c6648..e03aad5b49 100644 --- a/scm-ui/src/users/components/DeleteUser.test.js +++ b/scm-ui/src/users/components/DeleteUser.test.js @@ -2,7 +2,7 @@ import React from "react"; import { mount, shallow } from "enzyme"; import "../../tests/enzyme"; import "../../../tests/i18n"; -import DeleteUser from "../DeleteUser"; +import DeleteUser from "./DeleteUser"; import { confirmAlert } from "@scm-manager/ui-components"; jest.mock("@scm-manager/ui-components", () => ({ diff --git a/scm-ui/src/users/containers/SingleUser.js b/scm-ui/src/users/containers/SingleUser.js index dfc07d5942..b33e8dd906 100644 --- a/scm-ui/src/users/containers/SingleUser.js +++ b/scm-ui/src/users/containers/SingleUser.js @@ -17,14 +17,10 @@ import type { User } from "@scm-manager/ui-types"; import type { History } from "history"; import { fetchUserByName, - deleteUser, getUserByName, isFetchUserPending, - getFetchUserFailure, - isDeleteUserPending, - getDeleteUserFailure + getFetchUserFailure } from "../modules/users"; - import { EditUserNavLink, SetPasswordNavLink @@ -40,8 +36,7 @@ type Props = { error: Error, usersLink: string, - // dispatcher functions - deleteUser: (user: User, callback?: () => void) => void, + // dispatcher function fetchUserByName: (string, string) => void, // context objects @@ -55,14 +50,6 @@ class SingleUser extends React.Component<Props> { this.props.fetchUserByName(this.props.usersLink, this.props.name); } - userDeleted = () => { - this.props.history.push("/users"); - }; - - deleteUser = (user: User) => { - this.props.deleteUser(user, this.userDeleted); - }; - stripEndingSlash = (url: string) => { if (url.endsWith("/")) { return url.substring(0, url.length - 2); @@ -99,7 +86,7 @@ class SingleUser extends React.Component<Props> { <div className="column is-three-quarters"> <Route path={url} exact component={() => <Details user={user} />} /> <Route - path={`${url}/settings/edit`} + path={`${url}/settings/general`} component={() => <EditUser user={user} />} /> <Route @@ -115,10 +102,10 @@ class SingleUser extends React.Component<Props> { label={t("single-user.informationNavLink")} /> <SubNavigation - to={`${url}/settings/edit`} + to={`${url}/settings/general`} label={t("single-user.settingsNavLink")} > - <EditUserNavLink user={user} editUrl={`${url}/settings/edit`} /> + <EditUserNavLink user={user} editUrl={`${url}/settings/general`} /> <SetPasswordNavLink user={user} passwordUrl={`${url}/settings/password`} @@ -136,10 +123,8 @@ class SingleUser extends React.Component<Props> { const mapStateToProps = (state, ownProps) => { const name = ownProps.match.params.name; const user = getUserByName(state, name); - const loading = - isFetchUserPending(state, name) || isDeleteUserPending(state, name); - const error = - getFetchUserFailure(state, name) || getDeleteUserFailure(state, name); + const loading = isFetchUserPending(state, name); + const error = getFetchUserFailure(state, name); const usersLink = getUsersLink(state); return { usersLink, @@ -154,9 +139,6 @@ const mapDispatchToProps = dispatch => { return { fetchUserByName: (link: string, name: string) => { dispatch(fetchUserByName(link, name)); - }, - deleteUser: (user: User, callback?: () => void) => { - dispatch(deleteUser(user, callback)); } }; }; From df5434147c36593e3aa4479626cf39b1f91a467e Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Fri, 18 Jan 2019 15:52:34 +0100 Subject: [PATCH 013/112] refactoring + css fix + general config form changed --- scm-ui/public/locales/en/users.json | 46 ++++---- scm-ui/src/users/components/DeleteUser.js | 10 +- .../src/users/components/SetUserPassword.js | 4 +- scm-ui/src/users/components/UserForm.js | 106 ++++++++++-------- .../components/navLinks/EditUserNavLink.js | 2 +- .../components/navLinks/SetPasswordNavLink.js | 2 +- scm-ui/src/users/containers/SingleUser.js | 6 +- scm-ui/styles/scm.scss | 2 +- 8 files changed, 95 insertions(+), 83 deletions(-) diff --git a/scm-ui/public/locales/en/users.json b/scm-ui/public/locales/en/users.json index 957fb8f12c..0ec61c9870 100644 --- a/scm-ui/public/locales/en/users.json +++ b/scm-ui/public/locales/en/users.json @@ -17,29 +17,36 @@ "create-user-button": { "label": "Create" }, - "delete-user-button": { - "label": "Delete", - "confirm-alert": { - "title": "Delete user", - "message": "Do you really want to delete the user?", - "submit": "Yes", - "cancel": "No" - } - }, - "user-form": { - "submit": "Submit", - "deleteUser": "Delete User" - }, "add-user": { "title": "Create User", "subtitle": "Create a new user" }, "single-user": { - "navigationLabel": "User Navigation", - "informationNavLink": "Information", - "settingsNavLink": "Settings", - "editNavLink": "General", - "setPasswordNavLink": "Password", + "menu": { + "navigationLabel": "User Navigation", + "informationNavLink": "Information", + "settingsNavLink": "Settings", + "editNavLink": "General", + "setPasswordNavLink": "Password" + }, + "edit": { + "subtitle": "Edit User", + "button": "Submit" + }, + "delete": { + "subtitle": "Delete User", + "button": "Delete", + "confirm-alert": { + "title": "Delete user", + "message": "Do you really want to delete the user?", + "submit": "Yes", + "cancel": "No" + } + }, + "password": { + "button": "Set password", + "set-password-successful": "Password successfully set" + }, "errorTitle": "Error", "errorSubtitle": "Unknown user error" }, @@ -48,9 +55,6 @@ "name-invalid": "This name is invalid", "displayname-invalid": "This displayname is invalid" }, - "password": { - "set-password-successful": "Password successfully set" - }, "help": { "usernameHelpText": "Unique name of the user.", "displayNameHelpText": "Display name of the user.", diff --git a/scm-ui/src/users/components/DeleteUser.js b/scm-ui/src/users/components/DeleteUser.js index 775f243ac6..58d8d446dd 100644 --- a/scm-ui/src/users/components/DeleteUser.js +++ b/scm-ui/src/users/components/DeleteUser.js @@ -45,15 +45,15 @@ class DeleteUser extends React.Component<Props> { confirmDelete = () => { const { t } = this.props; confirmAlert({ - title: t("delete-user-button.confirm-alert.title"), - message: t("delete-user-button.confirm-alert.message"), + title: t("single-user.delete.confirm-alert.title"), + message: t("single-user.delete.confirm-alert.message"), buttons: [ { - label: t("delete-user-button.confirm-alert.submit"), + label: t("single-user.delete.confirm-alert.submit"), onClick: () => this.deleteUser() }, { - label: t("delete-user-button.confirm-alert.cancel"), + label: t("single-user.delete.confirm-alert.cancel"), onClick: () => null } ] @@ -71,7 +71,7 @@ class DeleteUser extends React.Component<Props> { if (!this.isDeletable()) { return null; } - return <DeleteButton label={t("user-form.deleteUser")} action={action} />; + return <DeleteButton label={t("single-user.delete.button")} action={action} />; } } diff --git a/scm-ui/src/users/components/SetUserPassword.js b/scm-ui/src/users/components/SetUserPassword.js index d318025f21..eadc2002c1 100644 --- a/scm-ui/src/users/components/SetUserPassword.js +++ b/scm-ui/src/users/components/SetUserPassword.js @@ -90,7 +90,7 @@ class SetUserPassword extends React.Component<Props, State> { message = ( <Notification type={"success"} - children={t("password.set-password-successful")} + children={t("single-user.password.set-password-successful")} onClose={() => this.onClose()} /> ); @@ -108,7 +108,7 @@ class SetUserPassword extends React.Component<Props, State> { <SubmitButton disabled={!this.state.passwordValid} loading={loading} - label={t("user-form.submit")} + label={t("single-user.password.button")} /> </form> ); diff --git a/scm-ui/src/users/components/UserForm.js b/scm-ui/src/users/components/UserForm.js index dde5d9bf18..eb8aa904ff 100644 --- a/scm-ui/src/users/components/UserForm.js +++ b/scm-ui/src/users/components/UserForm.js @@ -3,6 +3,7 @@ import React from "react"; import { translate } from "react-i18next"; import type { User } from "@scm-manager/ui-types"; import { + Subtitle, Checkbox, InputField, PasswordConfirmation, @@ -105,60 +106,67 @@ class UserForm extends React.Component<Props, State> { ); } return ( - <form onSubmit={this.submit}> + <> + <Subtitle subtitle={t("single-user.edit.subtitle")} /> + <form onSubmit={this.submit}> + <div className="columns"> + <div className="column is-half"> + {nameField} + <InputField + label={t("user.displayName")} + onChange={this.handleDisplayNameChange} + value={user ? user.displayName : ""} + validationError={this.state.displayNameValidationError} + errorMessage={t("validation.displayname-invalid")} + helpText={t("help.displayNameHelpText")} + /> + </div> + <div className="column is-half"> + <InputField + label={t("user.mail")} + onChange={this.handleEmailChange} + value={user ? user.mail : ""} + validationError={this.state.mailValidationError} + errorMessage={t("validation.mail-invalid")} + helpText={t("help.mailHelpText")} + /> + </div> + </div> + <div className="columns"> + <div className="column"> + {passwordChangeField} + <Checkbox + label={t("user.admin")} + onChange={this.handleAdminChange} + checked={user ? user.admin : false} + helpText={t("help.adminHelpText")} + /> + <Checkbox + label={t("user.active")} + onChange={this.handleActiveChange} + checked={user ? user.active : false} + helpText={t("help.activeHelpText")} + /> + </div> + </div> + <div className="columns"> + <div className="column"> + <SubmitButton + disabled={!this.isValid()} + loading={loading} + label={t("single-user.edit.button")} + /> + </div> + </div> + </form> + <hr /> + <Subtitle subtitle={t("single-user.delete.subtitle")} /> <div className="columns"> - <div className="column is-half"> - {nameField} - <InputField - label={t("user.displayName")} - onChange={this.handleDisplayNameChange} - value={user ? user.displayName : ""} - validationError={this.state.displayNameValidationError} - errorMessage={t("validation.displayname-invalid")} - helpText={t("help.displayNameHelpText")} - /> - </div> - <div className="column is-half"> - <InputField - label={t("user.mail")} - onChange={this.handleEmailChange} - value={user ? user.mail : ""} - validationError={this.state.mailValidationError} - errorMessage={t("validation.mail-invalid")} - helpText={t("help.mailHelpText")} - /> - </div> - </div> - <div className="columns"> - <div className="column"> - {passwordChangeField} - <Checkbox - label={t("user.admin")} - onChange={this.handleAdminChange} - checked={user ? user.admin : false} - helpText={t("help.adminHelpText")} - /> - <Checkbox - label={t("user.active")} - onChange={this.handleActiveChange} - checked={user ? user.active : false} - helpText={t("help.activeHelpText")} - /> - </div> - </div> - <div className="columns"> - <div className="column"> - <SubmitButton - disabled={!this.isValid()} - loading={loading} - label={t("user-form.submit")} - /> - </div> <div className="column"> <DeleteUser user={user} /> </div> </div> - </form> + </> ); } diff --git a/scm-ui/src/users/components/navLinks/EditUserNavLink.js b/scm-ui/src/users/components/navLinks/EditUserNavLink.js index 3689b445d8..b98a02c2db 100644 --- a/scm-ui/src/users/components/navLinks/EditUserNavLink.js +++ b/scm-ui/src/users/components/navLinks/EditUserNavLink.js @@ -17,7 +17,7 @@ class EditUserNavLink extends React.Component<Props> { if (!this.isEditable()) { return null; } - return <NavLink label={t("single-user.editNavLink")} to={editUrl} />; + return <NavLink label={t("single-user.menu.editNavLink")} to={editUrl} />; } isEditable = () => { diff --git a/scm-ui/src/users/components/navLinks/SetPasswordNavLink.js b/scm-ui/src/users/components/navLinks/SetPasswordNavLink.js index e9f28d841e..bea1bd2981 100644 --- a/scm-ui/src/users/components/navLinks/SetPasswordNavLink.js +++ b/scm-ui/src/users/components/navLinks/SetPasswordNavLink.js @@ -17,7 +17,7 @@ class ChangePasswordNavLink extends React.Component<Props> { if (!this.hasPermissionToSetPassword()) { return null; } - return <NavLink label={t("single-user.setPasswordNavLink")} to={passwordUrl} />; + return <NavLink label={t("single-user.menu.setPasswordNavLink")} to={passwordUrl} />; } hasPermissionToSetPassword = () => { diff --git a/scm-ui/src/users/containers/SingleUser.js b/scm-ui/src/users/containers/SingleUser.js index b33e8dd906..69c8a607e6 100644 --- a/scm-ui/src/users/containers/SingleUser.js +++ b/scm-ui/src/users/containers/SingleUser.js @@ -96,14 +96,14 @@ class SingleUser extends React.Component<Props> { </div> <div className="column"> <Navigation> - <Section label={t("single-user.navigationLabel")}> + <Section label={t("single-user.menu.navigationLabel")}> <NavLink to={`${url}`} - label={t("single-user.informationNavLink")} + label={t("single-user.menu.informationNavLink")} /> <SubNavigation to={`${url}/settings/general`} - label={t("single-user.settingsNavLink")} + label={t("single-user.menu.settingsNavLink")} > <EditUserNavLink user={user} editUrl={`${url}/settings/general`} /> <SetPasswordNavLink diff --git a/scm-ui/styles/scm.scss b/scm-ui/styles/scm.scss index df32e8eb7d..7ee8d427ba 100644 --- a/scm-ui/styles/scm.scss +++ b/scm-ui/styles/scm.scss @@ -291,7 +291,7 @@ $fa-font-path: "webfonts"; border-left: 1px solid #eee; border-right: 1px solid #eee; } - li:first-child { + > li:first-child { border-top: none; } li:last-child { From 9dc7882c54164b2f7ebe302cdfd6d2d2223e0a0d Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Fri, 18 Jan 2019 17:28:38 +0100 Subject: [PATCH 014/112] clarified langs, added repo settings menu, changed git config binding, fixed small css issue with div in navi --- .../scm-git-plugin/src/main/js/index.js | 2 +- scm-ui/public/locales/en/repos.json | 14 ++++-- .../src/repos/components/DeleteNavAction.js | 25 +++++++--- scm-ui/src/repos/components/EditNavLink.js | 2 +- .../repos/components/PermissionsNavLink.js | 2 +- .../components/PermissionsNavLink.test.js | 2 +- .../repos/components/form/RepositoryForm.js | 48 ++++++++++--------- scm-ui/src/repos/containers/Edit.js | 2 + scm-ui/src/repos/containers/RepositoryRoot.js | 38 ++++++++------- scm-ui/src/users/components/DeleteUser.js | 29 ++++++++--- scm-ui/src/users/components/UserForm.js | 8 ---- scm-ui/src/users/containers/EditUser.js | 3 ++ scm-ui/src/users/containers/SingleUser.js | 10 ++-- scm-ui/styles/scm.scss | 3 ++ 14 files changed, 116 insertions(+), 72 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/js/index.js b/scm-plugins/scm-git-plugin/src/main/js/index.js index a066247dde..5534d2061a 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/index.js +++ b/scm-plugins/scm-git-plugin/src/main/js/index.js @@ -28,7 +28,7 @@ binder.bind( binder.bind("repos.repository-avatar", GitAvatar, gitPredicate); cfgBinder.bindRepository( - "/configuration", + "/settings/configuration", "scm-git-plugin.repo-config.link", "configuration", RepositoryConfig diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 68f5b4a53b..4693dedfd6 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -17,11 +17,15 @@ "create-button": "Create" }, "repository-root": { - "navigationLabel": "Repository Navigation", - "historyNavLink": "Commits", - "informationNavLink": "Information", - "permissionsNavLink": "Permissions", - "sourcesNavLink": "Sources", + "menu": { + "navigationLabel": "Repository Navigation", + "informationNavLink": "Information", + "historyNavLink": "Commits", + "sourcesNavLink": "Sources", + "settingsNavLink": "Settings", + "editNavLink": "General", + "permissionsNavLink": "Permissions" + }, "errorTitle": "Error", "errorSubtitle": "Unknown repository error" }, diff --git a/scm-ui/src/repos/components/DeleteNavAction.js b/scm-ui/src/repos/components/DeleteNavAction.js index c2369a5bfb..a81cb17f66 100644 --- a/scm-ui/src/repos/components/DeleteNavAction.js +++ b/scm-ui/src/repos/components/DeleteNavAction.js @@ -1,7 +1,7 @@ //@flow import React from "react"; import { translate } from "react-i18next"; -import { NavAction, confirmAlert } from "@scm-manager/ui-components"; +import { Subtitle, DeleteButton, confirmAlert } from "@scm-manager/ui-components"; import type { Repository } from "@scm-manager/ui-types"; type Props = { @@ -25,15 +25,15 @@ class DeleteNavAction extends React.Component<Props> { confirmDelete = () => { const { t } = this.props; confirmAlert({ - title: t("delete-nav-action.confirm-alert.title"), - message: t("delete-nav-action.confirm-alert.message"), + title: t("repository.delete.confirm-alert.title"), + message: t("repository.delete.confirm-alert.message"), buttons: [ { - label: t("delete-nav-action.confirm-alert.submit"), + label: t("repository.delete.confirm-alert.submit"), onClick: () => this.delete() }, { - label: t("delete-nav-action.confirm-alert.cancel"), + label: t("repository.delete.confirm-alert.cancel"), onClick: () => null } ] @@ -51,7 +51,20 @@ class DeleteNavAction extends React.Component<Props> { if (!this.isDeletable()) { return null; } - return <NavAction label={t("delete-nav-action.label")} action={action} />; + + return ( + <> + <Subtitle subtitle={t("repository.delete.subtitle")} /> + <div className="columns"> + <div className="column"> + <DeleteButton + label={t("repository.delete.button")} + action={action} + /> + </div> + </div> + </> + ); } } diff --git a/scm-ui/src/repos/components/EditNavLink.js b/scm-ui/src/repos/components/EditNavLink.js index 1a49fdee81..2163624e4d 100644 --- a/scm-ui/src/repos/components/EditNavLink.js +++ b/scm-ui/src/repos/components/EditNavLink.js @@ -15,7 +15,7 @@ class EditNavLink extends React.Component<Props> { return null; } const { editUrl, t } = this.props; - return <NavLink to={editUrl} label={t("edit-nav-link.label")} />; + return <NavLink to={editUrl} label={t("repository-root.menu.editNavLink")} />; } } diff --git a/scm-ui/src/repos/components/PermissionsNavLink.js b/scm-ui/src/repos/components/PermissionsNavLink.js index 937980fd3d..364c274f6b 100644 --- a/scm-ui/src/repos/components/PermissionsNavLink.js +++ b/scm-ui/src/repos/components/PermissionsNavLink.js @@ -20,7 +20,7 @@ class PermissionsNavLink extends React.Component<Props> { } const { permissionUrl, t } = this.props; return ( - <NavLink to={permissionUrl} label={t("repository-root.permissionsNavLink")} /> + <NavLink to={permissionUrl} label={t("repository-root.menu.permissionsNavLink")} /> ); } } diff --git a/scm-ui/src/repos/components/PermissionsNavLink.test.js b/scm-ui/src/repos/components/PermissionsNavLink.test.js index 450c7f49e6..5dddfe0cf4 100644 --- a/scm-ui/src/repos/components/PermissionsNavLink.test.js +++ b/scm-ui/src/repos/components/PermissionsNavLink.test.js @@ -33,6 +33,6 @@ describe("PermissionsNavLink", () => { <PermissionsNavLink repository={repository} permissionUrl="" />, options.get() ); - expect(navLink.text()).toBe("repository-root.permissions"); + expect(navLink.text()).toBe("repository-root.menu.permissions"); }); }); diff --git a/scm-ui/src/repos/components/form/RepositoryForm.js b/scm-ui/src/repos/components/form/RepositoryForm.js index 8f5d932778..cc0b409175 100644 --- a/scm-ui/src/repos/components/form/RepositoryForm.js +++ b/scm-ui/src/repos/components/form/RepositoryForm.js @@ -2,6 +2,7 @@ import React from "react"; import { translate } from "react-i18next"; import { + Subtitle, InputField, Select, SubmitButton, @@ -82,29 +83,32 @@ class RepositoryForm extends React.Component<Props, State> { const repository = this.state.repository; return ( - <form onSubmit={this.submit}> - {this.renderCreateOnlyFields()} - <InputField - label={t("repository.contact")} - onChange={this.handleContactChange} - value={repository ? repository.contact : ""} - validationError={this.state.contactValidationError} - errorMessage={t("validation.contact-invalid")} - helpText={t("help.contactHelpText")} - /> + <> + <Subtitle subtitle={t("repository.edit.subtitle")} /> + <form onSubmit={this.submit}> + {this.renderCreateOnlyFields()} + <InputField + label={t("repository.contact")} + onChange={this.handleContactChange} + value={repository ? repository.contact : ""} + validationError={this.state.contactValidationError} + errorMessage={t("validation.contact-invalid")} + helpText={t("help.contactHelpText")} + /> - <Textarea - label={t("repository.description")} - onChange={this.handleDescriptionChange} - value={repository ? repository.description : ""} - helpText={t("help.descriptionHelpText")} - /> - <SubmitButton - disabled={!this.isValid()} - loading={loading} - label={t("repository-form.submit")} - /> - </form> + <Textarea + label={t("repository.description")} + onChange={this.handleDescriptionChange} + value={repository ? repository.description : ""} + helpText={t("help.descriptionHelpText")} + /> + <SubmitButton + disabled={!this.isValid()} + loading={loading} + label={t("repository-form.submit")} + /> + </form> + </> ); } diff --git a/scm-ui/src/repos/containers/Edit.js b/scm-ui/src/repos/containers/Edit.js index 816dae8de9..7341bcbd3e 100644 --- a/scm-ui/src/repos/containers/Edit.js +++ b/scm-ui/src/repos/containers/Edit.js @@ -48,6 +48,8 @@ class Edit extends React.Component<Props> { this.props.modifyRepo(repo, this.repoModified); }} /> + <hr /> + <p>TODO: DeleteRepo hier einbinden. Aktuell heißt es noch DeleteNavAction</p> </div> ); } diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 9fcae12c53..1a7ca7eaea 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -6,7 +6,7 @@ import {connect} from "react-redux"; import {Route, Switch} from "react-router-dom"; import type {Repository} from "@scm-manager/ui-types"; -import {ErrorPage, Loading, Navigation, NavLink, Page, Section} from "@scm-manager/ui-components"; +import {ErrorPage, Loading, Navigation, SubNavigation, NavLink, Page, Section} from "@scm-manager/ui-components"; import {translate} from "react-i18next"; import RepositoryDetails from "../components/RepositoryDetails"; import DeleteNavAction from "../components/DeleteNavAction"; @@ -109,11 +109,11 @@ class RepositoryRoot extends React.Component<Props> { component={() => <RepositoryDetails repository={repository} />} /> <Route - path={`${url}/edit`} + path={`${url}/settings/general`} component={() => <Edit repository={repository} />} /> <Route - path={`${url}/permissions`} + path={`${url}/settings/permissions`} render={() => ( <Permissions namespace={this.props.repository.namespace} @@ -168,13 +168,13 @@ class RepositoryRoot extends React.Component<Props> { </div> <div className="column"> <Navigation> - <Section label={t("repository-root.navigationLabel")}> - <NavLink to={url} label={t("repository-root.informationNavLink")} /> + <Section label={t("repository-root.menu.navigationLabel")}> + <NavLink to={url} label={t("repository-root.menu.informationNavLink")} /> <RepositoryNavLink repository={repository} linkName="changesets" to={`${url}/changesets/`} - label={t("repository-root.historyNavLink")} + label={t("repository-root.menu.historyNavLink")} activeWhenMatch={this.matches} activeOnlyWhenExact={false} /> @@ -182,18 +182,24 @@ class RepositoryRoot extends React.Component<Props> { repository={repository} linkName="sources" to={`${url}/sources`} - label={t("repository-root.sourcesNavLink")} + label={t("repository-root.menu.sourcesNavLink")} activeOnlyWhenExact={false} /> - <PermissionsNavLink - permissionUrl={`${url}/permissions`} - repository={repository} - /> - <ExtensionPoint - name="repository.navigation" - props={extensionProps} - renderAll={true} - /> + <SubNavigation + to={`${url}/settings/general`} + label={t("repository-root.menu.settingsNavLink")} + > + <EditNavLink repository={repository} editUrl={`${url}/settings/general`} /> + <PermissionsNavLink + permissionUrl={`${url}/settings/permissions`} + repository={repository} + /> + <ExtensionPoint + name="repository.navigation" + props={extensionProps} + renderAll={true} + /> + </SubNavigation> </Section> </Navigation> </div> diff --git a/scm-ui/src/users/components/DeleteUser.js b/scm-ui/src/users/components/DeleteUser.js index 58d8d446dd..1a250fa58d 100644 --- a/scm-ui/src/users/components/DeleteUser.js +++ b/scm-ui/src/users/components/DeleteUser.js @@ -2,15 +2,20 @@ import React from "react"; import { translate } from "react-i18next"; import type { User } from "@scm-manager/ui-types"; -import { DeleteButton, confirmAlert } from "@scm-manager/ui-components"; -import {connect} from "react-redux"; import { - deleteUser, fetchUserByName, + Subtitle, + DeleteButton, + confirmAlert +} from "@scm-manager/ui-components"; +import { connect } from "react-redux"; +import { + deleteUser, + fetchUserByName, getDeleteUserFailure, getUserByName, - isDeleteUserPending, + isDeleteUserPending } from "../modules/users"; -import type {History} from "history"; +import type { History } from "history"; type Props = { user: User, @@ -71,7 +76,19 @@ class DeleteUser extends React.Component<Props> { if (!this.isDeletable()) { return null; } - return <DeleteButton label={t("single-user.delete.button")} action={action} />; + return ( + <> + <Subtitle subtitle={t("single-user.delete.subtitle")} /> + <div className="columns"> + <div className="column"> + <DeleteButton + label={t("single-user.delete.button")} + action={action} + /> + </div> + </div> + </> + ); } } diff --git a/scm-ui/src/users/components/UserForm.js b/scm-ui/src/users/components/UserForm.js index eb8aa904ff..0f6119ef94 100644 --- a/scm-ui/src/users/components/UserForm.js +++ b/scm-ui/src/users/components/UserForm.js @@ -11,7 +11,6 @@ import { validation as validator } from "@scm-manager/ui-components"; import * as userValidator from "./userValidation"; -import DeleteUser from "./DeleteUser"; type Props = { submitForm: User => void, @@ -159,13 +158,6 @@ class UserForm extends React.Component<Props, State> { </div> </div> </form> - <hr /> - <Subtitle subtitle={t("single-user.delete.subtitle")} /> - <div className="columns"> - <div className="column"> - <DeleteUser user={user} /> - </div> - </div> </> ); } diff --git a/scm-ui/src/users/containers/EditUser.js b/scm-ui/src/users/containers/EditUser.js index 55062ecb5b..a8f3276471 100644 --- a/scm-ui/src/users/containers/EditUser.js +++ b/scm-ui/src/users/containers/EditUser.js @@ -3,6 +3,7 @@ import React from "react"; import { connect } from "react-redux"; import { withRouter } from "react-router-dom"; import UserForm from "./../components/UserForm"; +import DeleteUser from "./../components/DeleteUser"; import type { User } from "@scm-manager/ui-types"; import { modifyUser, @@ -49,6 +50,8 @@ class EditUser extends React.Component<Props> { user={user} loading={loading} /> + <hr /> + <DeleteUser user={user} /> </div> ); } diff --git a/scm-ui/src/users/containers/SingleUser.js b/scm-ui/src/users/containers/SingleUser.js index 69c8a607e6..321e117976 100644 --- a/scm-ui/src/users/containers/SingleUser.js +++ b/scm-ui/src/users/containers/SingleUser.js @@ -21,10 +21,7 @@ import { isFetchUserPending, getFetchUserFailure } from "../modules/users"; -import { - EditUserNavLink, - SetPasswordNavLink -} from "./../components/navLinks"; +import { EditUserNavLink, SetPasswordNavLink } from "./../components/navLinks"; import { translate } from "react-i18next"; import { getUsersLink } from "../../modules/indexResource"; import SetUserPassword from "../components/SetUserPassword"; @@ -105,7 +102,10 @@ class SingleUser extends React.Component<Props> { to={`${url}/settings/general`} label={t("single-user.menu.settingsNavLink")} > - <EditUserNavLink user={user} editUrl={`${url}/settings/general`} /> + <EditUserNavLink + user={user} + editUrl={`${url}/settings/general`} + /> <SetPasswordNavLink user={user} passwordUrl={`${url}/settings/password`} diff --git a/scm-ui/styles/scm.scss b/scm-ui/styles/scm.scss index 7ee8d427ba..3ceab48f9f 100644 --- a/scm-ui/styles/scm.scss +++ b/scm-ui/styles/scm.scss @@ -297,4 +297,7 @@ $fa-font-path: "webfonts"; li:last-child { border-bottom: 1px solid #eee; } + div { + margin-bottom: 0; + } } From ac28d7d01974535681db93a209336351fa72caf4 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 23 Jan 2019 09:10:43 +0100 Subject: [PATCH 015/112] undo changesetdetails changes --- .../components/changesets/ChangesetDetails.js | 41 ++++++++----------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/scm-ui/src/repos/components/changesets/ChangesetDetails.js b/scm-ui/src/repos/components/changesets/ChangesetDetails.js index 034ee36263..eb9d0992ee 100644 --- a/scm-ui/src/repos/components/changesets/ChangesetDetails.js +++ b/scm-ui/src/repos/components/changesets/ChangesetDetails.js @@ -46,15 +46,7 @@ class ChangesetDetails extends React.Component<Props> { return ( <div> <div className="content"> - <h4> - <ExtensionPoint - name="changesets.changeset.description" - props={{ changeset, value: description.title }} - renderAll={true} - > - {description.title} - </ExtensionPoint> - </h4> + <h4>{description.title}</h4> <article className="media"> <AvatarWrapper> <p className={classNames("image", "is-64x64", classes.spacing)}> @@ -75,23 +67,22 @@ class ChangesetDetails extends React.Component<Props> { </div> <div className="media-right">{this.renderTags()}</div> </article> - - <p> - {description.message.split("\n").map((item, key) => { - return ( - <span key={key}> - <ExtensionPoint - name="changesets.changeset.description" - props={{ changeset, value: item }} - renderAll={true} - > + <ExtensionPoint + name="changesets.changeset.description" + props={{ changeset, description }} + renderAll={true} + > + <p> + {description.message.split("\n").map((item, key) => { + return ( + <span key={key}> {item} - </ExtensionPoint> - <br /> - </span> - ); - })} - </p> + <br /> + </span> + ); + })} + </p> + </ExtensionPoint> </div> <div> <ChangesetDiff changeset={changeset} /> From 904f5851a7acbbf7fb67a589a295863ca3dbf002 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 23 Jan 2019 09:17:53 +0100 Subject: [PATCH 016/112] renamed edituser to generaluser --- .../{EditUserNavLink.js => GeneralUserNavLink.js} | 4 ++-- ...EditUserNavLink.test.js => GeneralUserNavLink.test.js} | 6 +++--- scm-ui/src/users/components/navLinks/index.js | 2 +- .../src/users/containers/{EditUser.js => GeneralUser.js} | 4 ++-- scm-ui/src/users/containers/SingleUser.js | 8 ++++---- 5 files changed, 12 insertions(+), 12 deletions(-) rename scm-ui/src/users/components/navLinks/{EditUserNavLink.js => GeneralUserNavLink.js} (82%) rename scm-ui/src/users/components/navLinks/{EditUserNavLink.test.js => GeneralUserNavLink.test.js} (67%) rename scm-ui/src/users/containers/{EditUser.js => GeneralUser.js} (96%) diff --git a/scm-ui/src/users/components/navLinks/EditUserNavLink.js b/scm-ui/src/users/components/navLinks/GeneralUserNavLink.js similarity index 82% rename from scm-ui/src/users/components/navLinks/EditUserNavLink.js rename to scm-ui/src/users/components/navLinks/GeneralUserNavLink.js index b98a02c2db..a6f1201152 100644 --- a/scm-ui/src/users/components/navLinks/EditUserNavLink.js +++ b/scm-ui/src/users/components/navLinks/GeneralUserNavLink.js @@ -10,7 +10,7 @@ type Props = { editUrl: String }; -class EditUserNavLink extends React.Component<Props> { +class GeneralUserNavLink extends React.Component<Props> { render() { const { t, editUrl } = this.props; @@ -25,4 +25,4 @@ class EditUserNavLink extends React.Component<Props> { }; } -export default translate("users")(EditUserNavLink); +export default translate("users")(GeneralUserNavLink); diff --git a/scm-ui/src/users/components/navLinks/EditUserNavLink.test.js b/scm-ui/src/users/components/navLinks/GeneralUserNavLink.test.js similarity index 67% rename from scm-ui/src/users/components/navLinks/EditUserNavLink.test.js rename to scm-ui/src/users/components/navLinks/GeneralUserNavLink.test.js index ec46c531a1..018e98f47e 100644 --- a/scm-ui/src/users/components/navLinks/EditUserNavLink.test.js +++ b/scm-ui/src/users/components/navLinks/GeneralUserNavLink.test.js @@ -2,14 +2,14 @@ import React from "react"; import { shallow } from "enzyme"; import "../../../tests/enzyme"; import "../../../tests/i18n"; -import EditUserNavLink from "./EditUserNavLink"; +import GeneralUserNavLink from "./GeneralUserNavLink"; it("should render nothing, if the edit link is missing", () => { const user = { _links: {} }; - const navLink = shallow(<EditUserNavLink user={user} editUrl='/user/edit'/>); + const navLink = shallow(<GeneralUserNavLink user={user} editUrl='/user/edit'/>); expect(navLink.text()).toBe(""); }); @@ -22,6 +22,6 @@ it("should render the navLink", () => { } }; - const navLink = shallow(<EditUserNavLink user={user} editUrl='/user/edit'/>); + const navLink = shallow(<GeneralUserNavLink user={user} editUrl='/user/edit'/>); expect(navLink.text()).not.toBe(""); }); diff --git a/scm-ui/src/users/components/navLinks/index.js b/scm-ui/src/users/components/navLinks/index.js index 64f7536c4c..64a266f923 100644 --- a/scm-ui/src/users/components/navLinks/index.js +++ b/scm-ui/src/users/components/navLinks/index.js @@ -1,2 +1,2 @@ -export { default as EditUserNavLink } from "./EditUserNavLink"; +export { default as GeneralUserNavLink } from "./GeneralUserNavLink"; export { default as SetPasswordNavLink } from "./SetPasswordNavLink"; diff --git a/scm-ui/src/users/containers/EditUser.js b/scm-ui/src/users/containers/GeneralUser.js similarity index 96% rename from scm-ui/src/users/containers/EditUser.js rename to scm-ui/src/users/containers/GeneralUser.js index a8f3276471..ac94b10148 100644 --- a/scm-ui/src/users/containers/EditUser.js +++ b/scm-ui/src/users/containers/GeneralUser.js @@ -27,7 +27,7 @@ type Props = { history: History }; -class EditUser extends React.Component<Props> { +class GeneralUser extends React.Component<Props> { componentDidMount() { const { modifyUserReset, user } = this.props; modifyUserReset(user); @@ -80,4 +80,4 @@ const mapStateToProps = (state, ownProps) => { export default connect( mapStateToProps, mapDispatchToProps -)(withRouter(EditUser)); +)(withRouter(GeneralUser)); diff --git a/scm-ui/src/users/containers/SingleUser.js b/scm-ui/src/users/containers/SingleUser.js index 321e117976..060c55bf70 100644 --- a/scm-ui/src/users/containers/SingleUser.js +++ b/scm-ui/src/users/containers/SingleUser.js @@ -12,7 +12,7 @@ import { } from "@scm-manager/ui-components"; import { Route } from "react-router"; import { Details } from "./../components/table"; -import EditUser from "./EditUser"; +import GeneralUser from "./GeneralUser"; import type { User } from "@scm-manager/ui-types"; import type { History } from "history"; import { @@ -21,7 +21,7 @@ import { isFetchUserPending, getFetchUserFailure } from "../modules/users"; -import { EditUserNavLink, SetPasswordNavLink } from "./../components/navLinks"; +import { GeneralUserNavLink, SetPasswordNavLink } from "./../components/navLinks"; import { translate } from "react-i18next"; import { getUsersLink } from "../../modules/indexResource"; import SetUserPassword from "../components/SetUserPassword"; @@ -84,7 +84,7 @@ class SingleUser extends React.Component<Props> { <Route path={url} exact component={() => <Details user={user} />} /> <Route path={`${url}/settings/general`} - component={() => <EditUser user={user} />} + component={() => <GeneralUser user={user} />} /> <Route path={`${url}/settings/password`} @@ -102,7 +102,7 @@ class SingleUser extends React.Component<Props> { to={`${url}/settings/general`} label={t("single-user.menu.settingsNavLink")} > - <EditUserNavLink + <GeneralUserNavLink user={user} editUrl={`${url}/settings/general`} /> From cb5e74e7915b10ec3bc6f450d8525192c5084f71 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 23 Jan 2019 10:08:15 +0100 Subject: [PATCH 017/112] renamed navlink to simple delete --- .../{DeleteNavAction.js => DeleteRepo.js} | 7 +++--- ...teNavAction.test.js => DeleteRepo.test.js} | 12 +++++----- .../containers/{Edit.js => GeneralRepo.js} | 20 ++++++++--------- scm-ui/src/repos/containers/RepositoryRoot.js | 8 +++---- scm-ui/src/users/components/DeleteUser.js | 22 +++++-------------- scm-ui/src/users/containers/GeneralUser.js | 22 +++++++++---------- 6 files changed, 38 insertions(+), 53 deletions(-) rename scm-ui/src/repos/components/{DeleteNavAction.js => DeleteRepo.js} (88%) rename scm-ui/src/repos/components/{DeleteNavAction.test.js => DeleteRepo.test.js} (83%) rename scm-ui/src/repos/containers/{Edit.js => GeneralRepo.js} (86%) diff --git a/scm-ui/src/repos/components/DeleteNavAction.js b/scm-ui/src/repos/components/DeleteRepo.js similarity index 88% rename from scm-ui/src/repos/components/DeleteNavAction.js rename to scm-ui/src/repos/components/DeleteRepo.js index a81cb17f66..512ec9a2f2 100644 --- a/scm-ui/src/repos/components/DeleteNavAction.js +++ b/scm-ui/src/repos/components/DeleteRepo.js @@ -7,19 +7,18 @@ import type { Repository } from "@scm-manager/ui-types"; type Props = { repository: Repository, confirmDialog?: boolean, - delete: Repository => void, // context props t: string => string }; -class DeleteNavAction extends React.Component<Props> { +class DeleteRepo extends React.Component<Props> { static defaultProps = { confirmDialog: true }; delete = () => { - this.props.delete(this.props.repository); + //this.props.delete(this.props.repository); }; confirmDelete = () => { @@ -68,4 +67,4 @@ class DeleteNavAction extends React.Component<Props> { } } -export default translate("repos")(DeleteNavAction); +export default translate("repos")(DeleteRepo); diff --git a/scm-ui/src/repos/components/DeleteNavAction.test.js b/scm-ui/src/repos/components/DeleteRepo.test.js similarity index 83% rename from scm-ui/src/repos/components/DeleteNavAction.test.js rename to scm-ui/src/repos/components/DeleteRepo.test.js index 7c2191864a..7985e02ef2 100644 --- a/scm-ui/src/repos/components/DeleteNavAction.test.js +++ b/scm-ui/src/repos/components/DeleteRepo.test.js @@ -2,7 +2,7 @@ import React from "react"; import { mount, shallow } from "enzyme"; import "../../tests/enzyme"; import "../../tests/i18n"; -import DeleteNavAction from "./DeleteNavAction"; +import DeleteRepo from "./DeleteRepo"; import { confirmAlert } from "@scm-manager/ui-components"; jest.mock("@scm-manager/ui-components", () => ({ @@ -10,14 +10,14 @@ jest.mock("@scm-manager/ui-components", () => ({ NavAction: require.requireActual("@scm-manager/ui-components").NavAction })); -describe("DeleteNavAction", () => { +describe("DeleteRepo", () => { it("should render nothing, if the delete link is missing", () => { const repository = { _links: {} }; const navLink = shallow( - <DeleteNavAction repository={repository} delete={() => {}} /> + <DeleteRepo repository={repository} delete={() => {}} /> ); expect(navLink.text()).toBe(""); }); @@ -32,7 +32,7 @@ describe("DeleteNavAction", () => { }; const navLink = mount( - <DeleteNavAction repository={repository} delete={() => {}} /> + <DeleteRepo repository={repository} delete={() => {}} /> ); expect(navLink.text()).not.toBe(""); }); @@ -47,7 +47,7 @@ describe("DeleteNavAction", () => { }; const navLink = mount( - <DeleteNavAction repository={repository} delete={() => {}} /> + <DeleteRepo repository={repository} delete={() => {}} /> ); navLink.find("a").simulate("click"); @@ -69,7 +69,7 @@ describe("DeleteNavAction", () => { } const navLink = mount( - <DeleteNavAction + <DeleteRepo repository={repository} confirmDialog={false} delete={capture} diff --git a/scm-ui/src/repos/containers/Edit.js b/scm-ui/src/repos/containers/GeneralRepo.js similarity index 86% rename from scm-ui/src/repos/containers/Edit.js rename to scm-ui/src/repos/containers/GeneralRepo.js index 7341bcbd3e..70cc9f9adc 100644 --- a/scm-ui/src/repos/containers/Edit.js +++ b/scm-ui/src/repos/containers/GeneralRepo.js @@ -1,8 +1,9 @@ // @flow import React from "react"; import { connect } from "react-redux"; -import { translate } from "react-i18next"; +import { withRouter } from "react-router-dom"; import RepositoryForm from "../components/form"; +import DeleteRepo from "../components/DeleteRepo"; import type { Repository } from "@scm-manager/ui-types"; import { modifyRepo, @@ -10,23 +11,22 @@ import { getModifyRepoFailure, modifyRepoReset } from "../modules/repos"; -import { withRouter } from "react-router-dom"; import type { History } from "history"; import { ErrorNotification } from "@scm-manager/ui-components"; type Props = { - repository: Repository, - modifyRepo: (Repository, () => void) => void, - modifyRepoReset: Repository => void, loading: boolean, error: Error, + modifyRepo: (Repository, () => void) => void, + modifyRepoReset: Repository => void, + // context props - t: string => string, + repository: Repository, history: History }; -class Edit extends React.Component<Props> { +class GeneralRepo extends React.Component<Props> { componentDidMount() { const { modifyRepoReset, repository } = this.props; modifyRepoReset(repository); @@ -37,7 +37,7 @@ class Edit extends React.Component<Props> { }; render() { - const { loading, error } = this.props; + const { loading, error, repository } = this.props; return ( <div> <ErrorNotification error={error} /> @@ -49,7 +49,7 @@ class Edit extends React.Component<Props> { }} /> <hr /> - <p>TODO: DeleteRepo hier einbinden. Aktuell heißt es noch DeleteNavAction</p> + <DeleteRepo repository={repository} /> </div> ); } @@ -79,4 +79,4 @@ const mapDispatchToProps = dispatch => { export default connect( mapStateToProps, mapDispatchToProps -)(translate("repos")(withRouter(Edit))); +)(withRouter(GeneralRepo)); diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 1a7ca7eaea..1de1a9dd89 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -9,12 +9,10 @@ import type {Repository} from "@scm-manager/ui-types"; import {ErrorPage, Loading, Navigation, SubNavigation, NavLink, Page, Section} from "@scm-manager/ui-components"; import {translate} from "react-i18next"; import RepositoryDetails from "../components/RepositoryDetails"; -import DeleteNavAction from "../components/DeleteNavAction"; -import Edit from "../containers/Edit"; +import GeneralRepo from "./GeneralRepo"; import Permissions from "../permissions/containers/Permissions"; import type {History} from "history"; -import EditNavLink from "../components/EditNavLink"; import BranchRoot from "./ChangesetsRoot"; import ChangesetView from "./ChangesetView"; @@ -110,7 +108,7 @@ class RepositoryRoot extends React.Component<Props> { /> <Route path={`${url}/settings/general`} - component={() => <Edit repository={repository} />} + component={() => <GeneralRepo repository={repository} />} /> <Route path={`${url}/settings/permissions`} @@ -189,7 +187,7 @@ class RepositoryRoot extends React.Component<Props> { to={`${url}/settings/general`} label={t("repository-root.menu.settingsNavLink")} > - <EditNavLink repository={repository} editUrl={`${url}/settings/general`} /> + <NavLink repository={repository} editUrl={`${url}/settings/general`} /> <PermissionsNavLink permissionUrl={`${url}/settings/permissions`} repository={repository} diff --git a/scm-ui/src/users/components/DeleteUser.js b/scm-ui/src/users/components/DeleteUser.js index 1a250fa58d..95fbe0aa1d 100644 --- a/scm-ui/src/users/components/DeleteUser.js +++ b/scm-ui/src/users/components/DeleteUser.js @@ -1,20 +1,8 @@ // @flow import React from "react"; import { translate } from "react-i18next"; +import { Subtitle, DeleteButton, confirmAlert } from "@scm-manager/ui-components"; import type { User } from "@scm-manager/ui-types"; -import { - Subtitle, - DeleteButton, - confirmAlert -} from "@scm-manager/ui-components"; -import { connect } from "react-redux"; -import { - deleteUser, - fetchUserByName, - getDeleteUserFailure, - getUserByName, - isDeleteUserPending -} from "../modules/users"; import type { History } from "history"; type Props = { @@ -31,6 +19,10 @@ type Props = { }; class DeleteUser extends React.Component<Props> { + static defaultProps = { + confirmDialog: true + }; + userDeleted = () => { this.props.history.push("/users"); }; @@ -39,10 +31,6 @@ class DeleteUser extends React.Component<Props> { this.props.deleteUser(user, this.userDeleted); }; - static defaultProps = { - confirmDialog: true - }; - deleteUser = () => { this.props.deleteUser(this.props.user); }; diff --git a/scm-ui/src/users/containers/GeneralUser.js b/scm-ui/src/users/containers/GeneralUser.js index ac94b10148..f386994460 100644 --- a/scm-ui/src/users/containers/GeneralUser.js +++ b/scm-ui/src/users/containers/GeneralUser.js @@ -2,8 +2,8 @@ import React from "react"; import { connect } from "react-redux"; import { withRouter } from "react-router-dom"; -import UserForm from "./../components/UserForm"; -import DeleteUser from "./../components/DeleteUser"; +import UserForm from "../components/UserForm"; +import DeleteUser from "../components/DeleteUser"; import type { User } from "@scm-manager/ui-types"; import { modifyUser, @@ -57,6 +57,15 @@ class GeneralUser extends React.Component<Props> { } } +const mapStateToProps = (state, ownProps) => { + const loading = isModifyUserPending(state, ownProps.user.name); + const error = getModifyUserFailure(state, ownProps.user.name); + return { + loading, + error + }; +}; + const mapDispatchToProps = dispatch => { return { modifyUser: (user: User, callback?: () => void) => { @@ -68,15 +77,6 @@ const mapDispatchToProps = dispatch => { }; }; -const mapStateToProps = (state, ownProps) => { - const loading = isModifyUserPending(state, ownProps.user.name); - const error = getModifyUserFailure(state, ownProps.user.name); - return { - loading, - error - }; -}; - export default connect( mapStateToProps, mapDispatchToProps From ff3044c365f2196332277c60bf3a2b2cd95ed863 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 23 Jan 2019 11:14:30 +0100 Subject: [PATCH 018/112] lang file anpassen + refactoring --- .../src/repos/changesets/ChangesetDiff.js | 2 +- scm-ui/public/locales/en/repos.json | 160 +++++----- scm-ui/public/locales/en/users.json | 78 +++-- scm-ui/src/repos/components/DeleteRepo.js | 12 +- scm-ui/src/repos/components/EditNavLink.js | 2 +- .../src/repos/components/EditNavLink.test.js | 2 +- .../repos/components/PermissionsNavLink.js | 2 +- .../repos/components/form/RepositoryForm.js | 47 ++- scm-ui/src/repos/containers/ChangesetView.js | 4 +- scm-ui/src/repos/containers/ChangesetsRoot.js | 2 +- scm-ui/src/repos/containers/Overview.js | 2 +- scm-ui/src/repos/containers/RepositoryRoot.js | 24 +- .../src/repos/sources/containers/Sources.js | 2 +- scm-ui/src/users/components/DeleteUser.js | 12 +- .../src/users/components/SetUserPassword.js | 4 +- scm-ui/src/users/components/UserForm.js | 4 +- .../components/buttons/CreateUserButton.js | 20 -- .../components/navLinks/GeneralUserNavLink.js | 2 +- .../components/navLinks/SetPasswordNavLink.js | 2 +- scm-ui/src/users/containers/AddUser.js | 4 +- scm-ui/src/users/containers/SingleUser.js | 10 +- scm-ui/src/users/containers/Users.js | 286 +++++++++--------- 22 files changed, 326 insertions(+), 357 deletions(-) delete mode 100644 scm-ui/src/users/components/buttons/CreateUserButton.js diff --git a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetDiff.js b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetDiff.js index 857ff8c827..232b0e3577 100644 --- a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetDiff.js +++ b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetDiff.js @@ -25,7 +25,7 @@ class ChangesetDiff extends React.Component<Props> { render() { const { changeset, t } = this.props; if (!this.isDiffSupported(changeset)) { - return <Notification type="danger">{t("changesets.diff.not-supported")}</Notification>; + return <Notification type="danger">{t("changesets.changeset.diffNotSupported")}</Notification>; } else { const url = this.createUrl(changeset); return <LoadingDiff url={url} />; diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 4693dedfd6..a7a7d8449f 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -11,12 +11,15 @@ "name-invalid": "The repository name is invalid", "contact-invalid": "Contact must be a valid mail address" }, - "overview": { - "title": "Repositories", - "subtitle": "Overview of available repositories", - "create-button": "Create" + "help": { + "nameHelpText": "The name of the repository. This name will be part of the repository url.", + "typeHelpText": "The type of the repository (e.g. Mercurial, Git or Subversion).", + "contactHelpText": "Email address of the person who is responsible for this repository.", + "descriptionHelpText": "A short description of the repository." }, - "repository-root": { + "repositoryRoot": { + "errorTitle": "Error", + "errorSubtitle": "Unknown repository error", "menu": { "navigationLabel": "Repository Navigation", "informationNavLink": "Information", @@ -25,29 +28,37 @@ "settingsNavLink": "Settings", "editNavLink": "General", "permissionsNavLink": "Permissions" - }, - "errorTitle": "Error", - "errorSubtitle": "Unknown repository error" + } + }, + "overview": { + "title": "Repositories", + "subtitle": "Overview of available repositories", + "createButton": "Create" }, "create": { "title": "Create Repository", "subtitle": "Create a new repository" }, - "repository-form": { - "submit": "Save" - }, - "edit-nav-link": { - "label": "Edit" - }, - "delete-nav-action": { - "label": "Delete", - "confirm-alert": { - "title": "Delete repository", - "message": "Do you really want to delete the repository?", - "submit": "Yes", - "cancel": "No" + "changesets": { + "errorTitle": "Error", + "errorSubtitle": "Could not fetch changesets", + "branchSelectorLabel": "Branches", + "changeset": { + "description": "Description", + "summary": "Changeset {{id}} was committed {{time}}", + "diffNotSupported": "Diff of changesets is not supported by the type of repository", + "id": "ID", + "contact": "Contact", + "date": "Date" + }, + "author": { + "name": "Author", + "mail": "Mail" } }, + "repositoryForm": { + "submit": "Save" + }, "sources": { "file-tree": { "name": "Name", @@ -67,71 +78,54 @@ "size": "Size" } }, - "changesets": { - "diff": { - "not-supported": "Diff of changesets is not supported by the type of repository" - }, + "permission": { + "user": "User", + "group": "Group", "error-title": "Error", - "error-subtitle": "Could not fetch changesets", - "changeset": { - "id": "ID", - "description": "Description", - "contact": "Contact", - "date": "Date", - "summary": "Changeset {{id}} was committed {{time}}" + "error-subtitle": "Unknown permissions error", + "name": "User or Group", + "type": "Type", + "group-permission": "Group Permission", + "user-permission": "User Permission", + "edit-permission": { + "delete-button": "Delete", + "save-button": "Save Changes" }, - "author": { - "name": "Author", - "mail": "Mail" + "delete-permission-button": { + "label": "Delete", + "confirm-alert": { + "title": "Delete permission", + "message": "Do you really want to delete the permission?", + "submit": "Yes", + "cancel": "No" + } + }, + "add-permission": { + "add-permission-heading": "Add new Permission", + "submit-button": "Submit", + "name-input-invalid": "Permission is not allowed to be empty! If it is not empty, your input name is invalid or it already exists!" + }, + "help": { + "groupPermissionHelpText": "States if a permission is a group permission.", + "nameHelpText": "Manage permissions for a specific user or group", + "typeHelpText": "READ = read; WRITE = read and write; OWNER = read, write and also the ability to manage the properties and permissions" + }, + "autocomplete": { + "no-group-options": "No group suggestion available", + "group-placeholder": "Enter group", + "no-user-options": "No user suggestion available", + "user-placeholder": "Enter user", + "loading": "Loading..." } }, - "branch-selector": { - "label": "Branches" - }, - "permission": { - "user": "User", - "group": "Group", - "error-title": "Error", - "error-subtitle": "Unknown permissions error", - "name": "User or Group", - "type": "Type", - "group-permission": "Group Permission", - "user-permission": "User Permission", - "edit-permission": { - "delete-button": "Delete", - "save-button": "Save Changes" - }, - "delete-permission-button": { - "label": "Delete", - "confirm-alert": { - "title": "Delete permission", - "message": "Do you really want to delete the permission?", - "submit": "Yes", - "cancel": "No" - } - }, - "add-permission": { - "add-permission-heading": "Add new Permission", - "submit-button": "Submit", - "name-input-invalid": "Permission is not allowed to be empty! If it is not empty, your input name is invalid or it already exists!" - }, - "help": { - "groupPermissionHelpText": "States if a permission is a group permission.", - "nameHelpText": "Manage permissions for a specific user or group", - "typeHelpText": "READ = read; WRITE = read and write; OWNER = read, write and also the ability to manage the properties and permissions" - }, - "autocomplete": { - "no-group-options": "No group suggestion available", - "group-placeholder": "Enter group", - "no-user-options": "No user suggestion available", - "user-placeholder": "Enter user", - "loading": "Loading..." - } - }, - "help": { - "nameHelpText": "The name of the repository. This name will be part of the repository url.", - "typeHelpText": "The type of the repository (e.g. Mercurial, Git or Subversion).", - "contactHelpText": "Email address of the person who is responsible for this repository.", - "descriptionHelpText": "A short description of the repository." + "delete": { + "subtitle": "Delete Repository", + "button": "Delete", + "confirmAlert": { + "title": "Delete repository", + "message": "Do you really want to delete the repository?", + "submit": "Yes", + "cancel": "No" + } } } diff --git a/scm-ui/public/locales/en/users.json b/scm-ui/public/locales/en/users.json index 0ec61c9870..394d23dbb0 100644 --- a/scm-ui/public/locales/en/users.json +++ b/scm-ui/public/locales/en/users.json @@ -10,46 +10,6 @@ "creationDate": "Creation Date", "lastModified": "Last Modified" }, - "users": { - "title": "Users", - "subtitle": "Create, read, update and delete users" - }, - "create-user-button": { - "label": "Create" - }, - "add-user": { - "title": "Create User", - "subtitle": "Create a new user" - }, - "single-user": { - "menu": { - "navigationLabel": "User Navigation", - "informationNavLink": "Information", - "settingsNavLink": "Settings", - "editNavLink": "General", - "setPasswordNavLink": "Password" - }, - "edit": { - "subtitle": "Edit User", - "button": "Submit" - }, - "delete": { - "subtitle": "Delete User", - "button": "Delete", - "confirm-alert": { - "title": "Delete user", - "message": "Do you really want to delete the user?", - "submit": "Yes", - "cancel": "No" - } - }, - "password": { - "button": "Set password", - "set-password-successful": "Password successfully set" - }, - "errorTitle": "Error", - "errorSubtitle": "Unknown user error" - }, "validation": { "mail-invalid": "This email is invalid", "name-invalid": "This name is invalid", @@ -61,5 +21,43 @@ "mailHelpText": "Email address of the user.", "adminHelpText": "An administrator is able to create, modify and delete repositories, groups and users.", "activeHelpText": "Activate or deactive the user." + }, + "users": { + "title": "Users", + "subtitle": "Create, read, update and delete users", + "createButton": "Create" + }, + "singleUser": { + "errorTitle": "Error", + "errorSubtitle": "Unknown user error", + "menu": { + "navigationLabel": "User Navigation", + "informationNavLink": "Information", + "settingsNavLink": "Settings", + "editNavLink": "General", + "setPasswordNavLink": "Password" + } + }, + "addUser": { + "title": "Create User", + "subtitle": "Create a new user" + }, + "delete": { + "subtitle": "Delete User", + "button": "Delete", + "confirm-alert": { + "title": "Delete user", + "message": "Do you really want to delete the user?", + "submit": "Yes", + "cancel": "No" + } + }, + "singleUserPassword": { + "button": "Set password", + "setPasswordSuccessful": "Password successfully set" + }, + "userForm": { + "subtitle": "Edit User", + "button": "Submit" } } diff --git a/scm-ui/src/repos/components/DeleteRepo.js b/scm-ui/src/repos/components/DeleteRepo.js index 512ec9a2f2..d1908f9daf 100644 --- a/scm-ui/src/repos/components/DeleteRepo.js +++ b/scm-ui/src/repos/components/DeleteRepo.js @@ -24,15 +24,15 @@ class DeleteRepo extends React.Component<Props> { confirmDelete = () => { const { t } = this.props; confirmAlert({ - title: t("repository.delete.confirm-alert.title"), - message: t("repository.delete.confirm-alert.message"), + title: t("delete.confirmAlert.title"), + message: t("delete.confirmAlert.message"), buttons: [ { - label: t("repository.delete.confirm-alert.submit"), + label: t("delete.confirmAlert.submit"), onClick: () => this.delete() }, { - label: t("repository.delete.confirm-alert.cancel"), + label: t("delete.confirmAlert.cancel"), onClick: () => null } ] @@ -53,11 +53,11 @@ class DeleteRepo extends React.Component<Props> { return ( <> - <Subtitle subtitle={t("repository.delete.subtitle")} /> + <Subtitle subtitle={t("delete.subtitle")} /> <div className="columns"> <div className="column"> <DeleteButton - label={t("repository.delete.button")} + label={t("delete.button")} action={action} /> </div> diff --git a/scm-ui/src/repos/components/EditNavLink.js b/scm-ui/src/repos/components/EditNavLink.js index 2163624e4d..b06cd8d96e 100644 --- a/scm-ui/src/repos/components/EditNavLink.js +++ b/scm-ui/src/repos/components/EditNavLink.js @@ -15,7 +15,7 @@ class EditNavLink extends React.Component<Props> { return null; } const { editUrl, t } = this.props; - return <NavLink to={editUrl} label={t("repository-root.menu.editNavLink")} />; + return <NavLink to={editUrl} label={t("repositoryRoot.menu.editNavLink")} />; } } diff --git a/scm-ui/src/repos/components/EditNavLink.test.js b/scm-ui/src/repos/components/EditNavLink.test.js index 935b7cf928..080440cc88 100644 --- a/scm-ui/src/repos/components/EditNavLink.test.js +++ b/scm-ui/src/repos/components/EditNavLink.test.js @@ -33,6 +33,6 @@ describe("EditNavLink", () => { <EditNavLink repository={repository} editUrl="" />, options.get() ); - expect(navLink.text()).toBe("edit-nav-link.label"); + expect(navLink.text()).toBe("repositoryRoot.menu.editNavLink"); }); }); diff --git a/scm-ui/src/repos/components/PermissionsNavLink.js b/scm-ui/src/repos/components/PermissionsNavLink.js index 364c274f6b..773ad94246 100644 --- a/scm-ui/src/repos/components/PermissionsNavLink.js +++ b/scm-ui/src/repos/components/PermissionsNavLink.js @@ -20,7 +20,7 @@ class PermissionsNavLink extends React.Component<Props> { } const { permissionUrl, t } = this.props; return ( - <NavLink to={permissionUrl} label={t("repository-root.menu.permissionsNavLink")} /> + <NavLink to={permissionUrl} label={t("repositoryRoot.menu.permissionsNavLink")} /> ); } } diff --git a/scm-ui/src/repos/components/form/RepositoryForm.js b/scm-ui/src/repos/components/form/RepositoryForm.js index cc0b409175..a97c76af8b 100644 --- a/scm-ui/src/repos/components/form/RepositoryForm.js +++ b/scm-ui/src/repos/components/form/RepositoryForm.js @@ -83,32 +83,29 @@ class RepositoryForm extends React.Component<Props, State> { const repository = this.state.repository; return ( - <> - <Subtitle subtitle={t("repository.edit.subtitle")} /> - <form onSubmit={this.submit}> - {this.renderCreateOnlyFields()} - <InputField - label={t("repository.contact")} - onChange={this.handleContactChange} - value={repository ? repository.contact : ""} - validationError={this.state.contactValidationError} - errorMessage={t("validation.contact-invalid")} - helpText={t("help.contactHelpText")} - /> + <form onSubmit={this.submit}> + {this.renderCreateOnlyFields()} + <InputField + label={t("repository.contact")} + onChange={this.handleContactChange} + value={repository ? repository.contact : ""} + validationError={this.state.contactValidationError} + errorMessage={t("validation.contact-invalid")} + helpText={t("help.contactHelpText")} + /> - <Textarea - label={t("repository.description")} - onChange={this.handleDescriptionChange} - value={repository ? repository.description : ""} - helpText={t("help.descriptionHelpText")} - /> - <SubmitButton - disabled={!this.isValid()} - loading={loading} - label={t("repository-form.submit")} - /> - </form> - </> + <Textarea + label={t("repository.description")} + onChange={this.handleDescriptionChange} + value={repository ? repository.description : ""} + helpText={t("help.descriptionHelpText")} + /> + <SubmitButton + disabled={!this.isValid()} + loading={loading} + label={t("repositoryForm.submit")} + /> + </form> ); } diff --git a/scm-ui/src/repos/containers/ChangesetView.js b/scm-ui/src/repos/containers/ChangesetView.js index 80ab0b71d6..dc53b5d798 100644 --- a/scm-ui/src/repos/containers/ChangesetView.js +++ b/scm-ui/src/repos/containers/ChangesetView.js @@ -37,8 +37,8 @@ class ChangesetView extends React.Component<Props> { if (error) { return ( <ErrorPage - title={t("changeset-error.title")} - subtitle={t("changeset-error.subtitle")} + title={t("changesets.errorTitle")} + subtitle={t("changesets.errorSubtitle")} error={error} /> ); diff --git a/scm-ui/src/repos/containers/ChangesetsRoot.js b/scm-ui/src/repos/containers/ChangesetsRoot.js index 2eea64b8e1..c49904762a 100644 --- a/scm-ui/src/repos/containers/ChangesetsRoot.js +++ b/scm-ui/src/repos/containers/ChangesetsRoot.js @@ -101,7 +101,7 @@ class BranchRoot extends React.Component<Props> { if (repository._links.branches) { return ( <BranchSelector - label={t("branch-selector.label")} + label={t("changesets.branchSelectorLabel")} branches={branches} selectedBranch={selected} selected={(b: Branch) => { diff --git a/scm-ui/src/repos/containers/Overview.js b/scm-ui/src/repos/containers/Overview.js index bbafe14539..598b6c94f2 100644 --- a/scm-ui/src/repos/containers/Overview.js +++ b/scm-ui/src/repos/containers/Overview.js @@ -90,7 +90,7 @@ class Overview extends React.Component<Props> { if (showCreateButton) { return ( <CreateButton - label={t("overview.create-button")} + label={t("overview.createButton")} link="/repos/create" /> ); diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 1de1a9dd89..fcbe5dfee1 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -78,8 +78,8 @@ class RepositoryRoot extends React.Component<Props> { if (error) { return ( <ErrorPage - title={t("repository-root.errorTitle")} - subtitle={t("repository-root.errorSubtitle")} + title={t("repositoryRoot.errorTitle")} + subtitle={t("repositoryRoot.errorSubtitle")} error={error} /> ); @@ -166,13 +166,13 @@ class RepositoryRoot extends React.Component<Props> { </div> <div className="column"> <Navigation> - <Section label={t("repository-root.menu.navigationLabel")}> - <NavLink to={url} label={t("repository-root.menu.informationNavLink")} /> + <Section label={t("repositoryRoot.menu.navigationLabel")}> + <NavLink to={url} label={t("repositoryRoot.menu.informationNavLink")} /> <RepositoryNavLink repository={repository} linkName="changesets" to={`${url}/changesets/`} - label={t("repository-root.menu.historyNavLink")} + label={t("repositoryRoot.menu.historyNavLink")} activeWhenMatch={this.matches} activeOnlyWhenExact={false} /> @@ -180,23 +180,23 @@ class RepositoryRoot extends React.Component<Props> { repository={repository} linkName="sources" to={`${url}/sources`} - label={t("repository-root.menu.sourcesNavLink")} + label={t("repositoryRoot.menu.sourcesNavLink")} activeOnlyWhenExact={false} /> + <ExtensionPoint + name="repository.navigation" + props={extensionProps} + renderAll={true} + /> <SubNavigation to={`${url}/settings/general`} - label={t("repository-root.menu.settingsNavLink")} + label={t("repositoryRoot.menu.settingsNavLink")} > <NavLink repository={repository} editUrl={`${url}/settings/general`} /> <PermissionsNavLink permissionUrl={`${url}/settings/permissions`} repository={repository} /> - <ExtensionPoint - name="repository.navigation" - props={extensionProps} - renderAll={true} - /> </SubNavigation> </Section> </Navigation> diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index 810e2309ee..66c1b7bdf5 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -118,7 +118,7 @@ class Sources extends React.Component<Props> { <BranchSelector branches={branches} selectedBranch={revision} - label={t("branch-selector.label")} + label={t("changesets.branchSelectorLabel")} selected={(b: Branch) => { this.branchSelected(b); }} diff --git a/scm-ui/src/users/components/DeleteUser.js b/scm-ui/src/users/components/DeleteUser.js index 95fbe0aa1d..ec3bf57a15 100644 --- a/scm-ui/src/users/components/DeleteUser.js +++ b/scm-ui/src/users/components/DeleteUser.js @@ -38,15 +38,15 @@ class DeleteUser extends React.Component<Props> { confirmDelete = () => { const { t } = this.props; confirmAlert({ - title: t("single-user.delete.confirm-alert.title"), - message: t("single-user.delete.confirm-alert.message"), + title: t("delete.confirm-alert.title"), + message: t("delete.confirm-alert.message"), buttons: [ { - label: t("single-user.delete.confirm-alert.submit"), + label: t("delete.confirm-alert.submit"), onClick: () => this.deleteUser() }, { - label: t("single-user.delete.confirm-alert.cancel"), + label: t("delete.confirm-alert.cancel"), onClick: () => null } ] @@ -66,11 +66,11 @@ class DeleteUser extends React.Component<Props> { } return ( <> - <Subtitle subtitle={t("single-user.delete.subtitle")} /> + <Subtitle subtitle={t("delete.subtitle")} /> <div className="columns"> <div className="column"> <DeleteButton - label={t("single-user.delete.button")} + label={t("delete.button")} action={action} /> </div> diff --git a/scm-ui/src/users/components/SetUserPassword.js b/scm-ui/src/users/components/SetUserPassword.js index eadc2002c1..a0fe844b0c 100644 --- a/scm-ui/src/users/components/SetUserPassword.js +++ b/scm-ui/src/users/components/SetUserPassword.js @@ -90,7 +90,7 @@ class SetUserPassword extends React.Component<Props, State> { message = ( <Notification type={"success"} - children={t("single-user.password.set-password-successful")} + children={t("singleUserPassword.setPasswordSuccessful")} onClose={() => this.onClose()} /> ); @@ -108,7 +108,7 @@ class SetUserPassword extends React.Component<Props, State> { <SubmitButton disabled={!this.state.passwordValid} loading={loading} - label={t("single-user.password.button")} + label={t("singleUserPassword.button")} /> </form> ); diff --git a/scm-ui/src/users/components/UserForm.js b/scm-ui/src/users/components/UserForm.js index 0f6119ef94..d01838f08a 100644 --- a/scm-ui/src/users/components/UserForm.js +++ b/scm-ui/src/users/components/UserForm.js @@ -106,7 +106,7 @@ class UserForm extends React.Component<Props, State> { } return ( <> - <Subtitle subtitle={t("single-user.edit.subtitle")} /> + <Subtitle subtitle={t("userForm.subtitle")} /> <form onSubmit={this.submit}> <div className="columns"> <div className="column is-half"> @@ -153,7 +153,7 @@ class UserForm extends React.Component<Props, State> { <SubmitButton disabled={!this.isValid()} loading={loading} - label={t("single-user.edit.button")} + label={t("userForm.button")} /> </div> </div> diff --git a/scm-ui/src/users/components/buttons/CreateUserButton.js b/scm-ui/src/users/components/buttons/CreateUserButton.js deleted file mode 100644 index f34820cd0d..0000000000 --- a/scm-ui/src/users/components/buttons/CreateUserButton.js +++ /dev/null @@ -1,20 +0,0 @@ -//@flow -import React from "react"; -import { translate } from "react-i18next"; -import { CreateButton } from "@scm-manager/ui-components"; - -// TODO remove -type Props = { - t: string => string -}; - -class CreateUserButton extends React.Component<Props> { - render() { - const { t } = this.props; - return ( - <CreateButton label={t("create-user-button.label")} link="/users/add" /> - ); - } -} - -export default translate("users")(CreateUserButton); diff --git a/scm-ui/src/users/components/navLinks/GeneralUserNavLink.js b/scm-ui/src/users/components/navLinks/GeneralUserNavLink.js index a6f1201152..d4bce5b8b5 100644 --- a/scm-ui/src/users/components/navLinks/GeneralUserNavLink.js +++ b/scm-ui/src/users/components/navLinks/GeneralUserNavLink.js @@ -17,7 +17,7 @@ class GeneralUserNavLink extends React.Component<Props> { if (!this.isEditable()) { return null; } - return <NavLink label={t("single-user.menu.editNavLink")} to={editUrl} />; + return <NavLink label={t("singleUser.menu.editNavLink")} to={editUrl} />; } isEditable = () => { diff --git a/scm-ui/src/users/components/navLinks/SetPasswordNavLink.js b/scm-ui/src/users/components/navLinks/SetPasswordNavLink.js index bea1bd2981..7dfd807a93 100644 --- a/scm-ui/src/users/components/navLinks/SetPasswordNavLink.js +++ b/scm-ui/src/users/components/navLinks/SetPasswordNavLink.js @@ -17,7 +17,7 @@ class ChangePasswordNavLink extends React.Component<Props> { if (!this.hasPermissionToSetPassword()) { return null; } - return <NavLink label={t("single-user.menu.setPasswordNavLink")} to={passwordUrl} />; + return <NavLink label={t("singleUser.menu.setPasswordNavLink")} to={passwordUrl} />; } hasPermissionToSetPassword = () => { diff --git a/scm-ui/src/users/containers/AddUser.js b/scm-ui/src/users/containers/AddUser.js index 1ee6fc759d..1946f7849a 100644 --- a/scm-ui/src/users/containers/AddUser.js +++ b/scm-ui/src/users/containers/AddUser.js @@ -47,8 +47,8 @@ class AddUser extends React.Component<Props> { return ( <Page - title={t("add-user.title")} - subtitle={t("add-user.subtitle")} + title={t("addUser.title")} + subtitle={t("addUser.subtitle")} error={error} showContentOnError={true} > diff --git a/scm-ui/src/users/containers/SingleUser.js b/scm-ui/src/users/containers/SingleUser.js index 060c55bf70..65d94a7395 100644 --- a/scm-ui/src/users/containers/SingleUser.js +++ b/scm-ui/src/users/containers/SingleUser.js @@ -64,8 +64,8 @@ class SingleUser extends React.Component<Props> { if (error) { return ( <ErrorPage - title={t("single-user.errorTitle")} - subtitle={t("single-user.errorSubtitle")} + title={t("singleUser.errorTitle")} + subtitle={t("singleUser.errorSubtitle")} error={error} /> ); @@ -93,14 +93,14 @@ class SingleUser extends React.Component<Props> { </div> <div className="column"> <Navigation> - <Section label={t("single-user.menu.navigationLabel")}> + <Section label={t("singleUser.menu.navigationLabel")}> <NavLink to={`${url}`} - label={t("single-user.menu.informationNavLink")} + label={t("singleUser.menu.informationNavLink")} /> <SubNavigation to={`${url}/settings/general`} - label={t("single-user.menu.settingsNavLink")} + label={t("singleUser.menu.settingsNavLink")} > <GeneralUserNavLink user={user} diff --git a/scm-ui/src/users/containers/Users.js b/scm-ui/src/users/containers/Users.js index cbb33dc68b..041ac226b4 100644 --- a/scm-ui/src/users/containers/Users.js +++ b/scm-ui/src/users/containers/Users.js @@ -1,143 +1,143 @@ -// @flow -import React from "react"; -import type { History } from "history"; -import { connect } from "react-redux"; -import { translate } from "react-i18next"; - -import { - fetchUsersByPage, - fetchUsersByLink, - getUsersFromState, - selectListAsCollection, - isPermittedToCreateUsers, - isFetchUsersPending, - getFetchUsersFailure -} from "../modules/users"; - -import { Page, Paginator } from "@scm-manager/ui-components"; -import { UserTable } from "./../components/table"; -import type { User, PagedCollection } from "@scm-manager/ui-types"; -import CreateUserButton from "../components/buttons/CreateUserButton"; -import { getUsersLink } from "../../modules/indexResource"; - -type Props = { - users: User[], - loading: boolean, - error: Error, - canAddUsers: boolean, - list: PagedCollection, - page: number, - usersLink: string, - - // context objects - t: string => string, - history: History, - - // dispatch functions - fetchUsersByPage: (link: string, page: number) => void, - fetchUsersByLink: (link: string) => void -}; - -class Users extends React.Component<Props> { - componentDidMount() { - this.props.fetchUsersByPage(this.props.usersLink, this.props.page); - } - - onPageChange = (link: string) => { - this.props.fetchUsersByLink(link); - }; - - /** - * reflect page transitions in the uri - */ - componentDidUpdate() { - const { page, list } = this.props; - if (list && (list.page || list.page === 0)) { - // backend starts paging by 0 - const statePage: number = list.page + 1; - if (page !== statePage) { - this.props.history.push(`/users/${statePage}`); - } - } - } - - render() { - const { users, loading, error, t } = this.props; - return ( - <Page - title={t("users.title")} - subtitle={t("users.subtitle")} - loading={loading || !users} - error={error} - > - <UserTable users={users} /> - {this.renderPaginator()} - {this.renderCreateButton()} - </Page> - ); - } - - renderPaginator() { - const { list } = this.props; - if (list) { - return <Paginator collection={list} onPageChange={this.onPageChange} />; - } - return null; - } - - renderCreateButton() { - if (this.props.canAddUsers) { - return <CreateUserButton />; - } else { - return; - } - } -} - -const getPageFromProps = props => { - let page = props.match.params.page; - if (page) { - page = parseInt(page, 10); - } else { - page = 1; - } - return page; -}; - -const mapStateToProps = (state, ownProps) => { - const users = getUsersFromState(state); - const loading = isFetchUsersPending(state); - const error = getFetchUsersFailure(state); - - const usersLink = getUsersLink(state); - - const page = getPageFromProps(ownProps); - const canAddUsers = isPermittedToCreateUsers(state); - const list = selectListAsCollection(state); - - return { - users, - loading, - error, - canAddUsers, - list, - page, - usersLink - }; -}; - -const mapDispatchToProps = dispatch => { - return { - fetchUsersByPage: (link: string, page: number) => { - dispatch(fetchUsersByPage(link, page)); - }, - fetchUsersByLink: (link: string) => { - dispatch(fetchUsersByLink(link)); - } - }; -}; - -export default connect( - mapStateToProps, - mapDispatchToProps -)(translate("users")(Users)); +// @flow +import React from "react"; +import type { History } from "history"; +import { connect } from "react-redux"; +import { translate } from "react-i18next"; + +import { + fetchUsersByPage, + fetchUsersByLink, + getUsersFromState, + selectListAsCollection, + isPermittedToCreateUsers, + isFetchUsersPending, + getFetchUsersFailure +} from "../modules/users"; + +import { Page, CreateButton, Paginator } from "@scm-manager/ui-components"; +import { UserTable } from "./../components/table"; +import type { User, PagedCollection } from "@scm-manager/ui-types"; +import { getUsersLink } from "../../modules/indexResource"; + +type Props = { + users: User[], + loading: boolean, + error: Error, + canAddUsers: boolean, + list: PagedCollection, + page: number, + usersLink: string, + + // context objects + t: string => string, + history: History, + + // dispatch functions + fetchUsersByPage: (link: string, page: number) => void, + fetchUsersByLink: (link: string) => void +}; + +class Users extends React.Component<Props> { + componentDidMount() { + this.props.fetchUsersByPage(this.props.usersLink, this.props.page); + } + + onPageChange = (link: string) => { + this.props.fetchUsersByLink(link); + }; + + /** + * reflect page transitions in the uri + */ + componentDidUpdate() { + const { page, list } = this.props; + if (list && (list.page || list.page === 0)) { + // backend starts paging by 0 + const statePage: number = list.page + 1; + if (page !== statePage) { + this.props.history.push(`/users/${statePage}`); + } + } + } + + render() { + const { users, loading, error, t } = this.props; + return ( + <Page + title={t("users.title")} + subtitle={t("users.subtitle")} + loading={loading || !users} + error={error} + > + <UserTable users={users} /> + {this.renderPaginator()} + {this.renderCreateButton()} + </Page> + ); + } + + renderPaginator() { + const { list } = this.props; + if (list) { + return <Paginator collection={list} onPageChange={this.onPageChange} />; + } + return null; + } + + renderCreateButton() { + const { t } = this.props; + if (this.props.canAddUsers) { + return <CreateButton label={t("users.createButton")} link="/users/add" />; + } else { + return; + } + } +} + +const getPageFromProps = props => { + let page = props.match.params.page; + if (page) { + page = parseInt(page, 10); + } else { + page = 1; + } + return page; +}; + +const mapStateToProps = (state, ownProps) => { + const users = getUsersFromState(state); + const loading = isFetchUsersPending(state); + const error = getFetchUsersFailure(state); + + const usersLink = getUsersLink(state); + + const page = getPageFromProps(ownProps); + const canAddUsers = isPermittedToCreateUsers(state); + const list = selectListAsCollection(state); + + return { + users, + loading, + error, + canAddUsers, + list, + page, + usersLink + }; +}; + +const mapDispatchToProps = dispatch => { + return { + fetchUsersByPage: (link: string, page: number) => { + dispatch(fetchUsersByPage(link, page)); + }, + fetchUsersByLink: (link: string) => { + dispatch(fetchUsersByLink(link)); + } + }; +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(translate("users")(Users)); From 17cf42caf071a984e9b1360090df72a6cba16319 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 23 Jan 2019 11:55:57 +0100 Subject: [PATCH 019/112] added deleteuser functionality --- scm-ui/src/users/components/DeleteUser.js | 45 +--------------------- scm-ui/src/users/containers/GeneralUser.js | 24 ++++++++++-- 2 files changed, 22 insertions(+), 47 deletions(-) diff --git a/scm-ui/src/users/components/DeleteUser.js b/scm-ui/src/users/components/DeleteUser.js index ec3bf57a15..7932e14558 100644 --- a/scm-ui/src/users/components/DeleteUser.js +++ b/scm-ui/src/users/components/DeleteUser.js @@ -3,19 +3,16 @@ import React from "react"; import { translate } from "react-i18next"; import { Subtitle, DeleteButton, confirmAlert } from "@scm-manager/ui-components"; import type { User } from "@scm-manager/ui-types"; -import type { History } from "history"; type Props = { user: User, confirmDialog?: boolean, // dispatcher functions - fetchUserByName: (string, string) => void, - deleteUser: (user: User, callback?: () => void) => void, + deleteUser: (user: User) => void, // context objects - t: string => string, - history: History + t: string => string }; class DeleteUser extends React.Component<Props> { @@ -23,14 +20,6 @@ class DeleteUser extends React.Component<Props> { confirmDialog: true }; - userDeleted = () => { - this.props.history.push("/users"); - }; - - deleteUser = (user: User) => { - this.props.deleteUser(user, this.userDeleted); - }; - deleteUser = () => { this.props.deleteUser(this.props.user); }; @@ -80,34 +69,4 @@ class DeleteUser extends React.Component<Props> { } } -/* -const mapStateToProps = (state, ownProps) => { - const name = ownProps.match.params.name; - const user = getUserByName(state, name); - const loading = isDeleteUserPending(state, name); - const error = getDeleteUserFailure(state, name); - return { - name, - user, - loading, - error - }; -}; - -const mapDispatchToProps = dispatch => { - return { - fetchUserByName: (link: string, name: string) => { - dispatch(fetchUserByName(link, name)); - }, - deleteUser: (user: User, callback?: () => void) => { - dispatch(deleteUser(user, callback)); - } - }; -}; - -export default connect( - mapStateToProps, - mapDispatchToProps -)(translate("users")(DeleteUser)); -*/ export default translate("users")(DeleteUser); diff --git a/scm-ui/src/users/containers/GeneralUser.js b/scm-ui/src/users/containers/GeneralUser.js index f386994460..cb578d6f1e 100644 --- a/scm-ui/src/users/containers/GeneralUser.js +++ b/scm-ui/src/users/containers/GeneralUser.js @@ -7,9 +7,12 @@ import DeleteUser from "../components/DeleteUser"; import type { User } from "@scm-manager/ui-types"; import { modifyUser, + deleteUser, isModifyUserPending, getModifyUserFailure, - modifyUserReset + modifyUserReset, + isDeleteUserPending, + getDeleteUserFailure } from "../modules/users"; import type { History } from "history"; import { ErrorNotification } from "@scm-manager/ui-components"; @@ -21,6 +24,7 @@ type Props = { // dispatch functions modifyUser: (user: User, callback?: () => void) => void, modifyUserReset: User => void, + deleteUser: (user: User, callback?: () => void) => void, // context objects user: User, @@ -32,6 +36,7 @@ class GeneralUser extends React.Component<Props> { const { modifyUserReset, user } = this.props; modifyUserReset(user); } + userModified = (user: User) => () => { this.props.history.push(`/user/${user.name}`); }; @@ -40,6 +45,14 @@ class GeneralUser extends React.Component<Props> { this.props.modifyUser(user, this.userModified(user)); }; + userDeleted = () => { + this.props.history.push("/users"); + }; + + deleteUser = (user: User) => { + this.props.deleteUser(user, this.userDeleted); + }; + render() { const { user, loading, error } = this.props; return ( @@ -51,15 +64,15 @@ class GeneralUser extends React.Component<Props> { loading={loading} /> <hr /> - <DeleteUser user={user} /> + <DeleteUser user={user} deleteUser={this.deleteUser} /> </div> ); } } const mapStateToProps = (state, ownProps) => { - const loading = isModifyUserPending(state, ownProps.user.name); - const error = getModifyUserFailure(state, ownProps.user.name); + const loading = isModifyUserPending(state, ownProps.user.name) || isDeleteUserPending(state, ownProps.user.name); + const error = getModifyUserFailure(state, ownProps.user.name) || getDeleteUserFailure(state, ownProps.user.name); return { loading, error @@ -73,6 +86,9 @@ const mapDispatchToProps = dispatch => { }, modifyUserReset: (user: User) => { dispatch(modifyUserReset(user)); + }, + deleteUser: (user: User, callback?: () => void) => { + dispatch(deleteUser(user, callback)); } }; }; From 27c71ec6a393d2a9574c9a605dcb9315e5583d5f Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 23 Jan 2019 12:07:28 +0100 Subject: [PATCH 020/112] added deleterepo functionality --- scm-ui/src/repos/components/DeleteRepo.js | 5 ++++- scm-ui/src/repos/containers/GeneralRepo.js | 16 +++++++++++++++- scm-ui/src/users/components/DeleteUser.js | 1 + 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/scm-ui/src/repos/components/DeleteRepo.js b/scm-ui/src/repos/components/DeleteRepo.js index d1908f9daf..57ee868586 100644 --- a/scm-ui/src/repos/components/DeleteRepo.js +++ b/scm-ui/src/repos/components/DeleteRepo.js @@ -8,6 +8,9 @@ type Props = { repository: Repository, confirmDialog?: boolean, + // dispatcher functions + delete: Repository => void, + // context props t: string => string }; @@ -18,7 +21,7 @@ class DeleteRepo extends React.Component<Props> { }; delete = () => { - //this.props.delete(this.props.repository); + this.props.delete(this.props.repository); }; confirmDelete = () => { diff --git a/scm-ui/src/repos/containers/GeneralRepo.js b/scm-ui/src/repos/containers/GeneralRepo.js index 70cc9f9adc..83c5d47b52 100644 --- a/scm-ui/src/repos/containers/GeneralRepo.js +++ b/scm-ui/src/repos/containers/GeneralRepo.js @@ -7,6 +7,7 @@ import DeleteRepo from "../components/DeleteRepo"; import type { Repository } from "@scm-manager/ui-types"; import { modifyRepo, + deleteRepo, isModifyRepoPending, getModifyRepoFailure, modifyRepoReset @@ -20,6 +21,7 @@ type Props = { modifyRepo: (Repository, () => void) => void, modifyRepoReset: Repository => void, + deleteRepo: (Repository, () => void) => void, // context props repository: Repository, @@ -31,11 +33,20 @@ class GeneralRepo extends React.Component<Props> { const { modifyRepoReset, repository } = this.props; modifyRepoReset(repository); } + repoModified = () => { const { history, repository } = this.props; history.push(`/repo/${repository.namespace}/${repository.name}`); }; + deleted = () => { + this.props.history.push("/repos"); + }; + + delete = (repository: Repository) => { + this.props.deleteRepo(repository, this.deleted); + }; + render() { const { loading, error, repository } = this.props; return ( @@ -49,7 +60,7 @@ class GeneralRepo extends React.Component<Props> { }} /> <hr /> - <DeleteRepo repository={repository} /> + <DeleteRepo repository={repository} delete={this.delete} /> </div> ); } @@ -72,6 +83,9 @@ const mapDispatchToProps = dispatch => { }, modifyRepoReset: (repo: Repository) => { dispatch(modifyRepoReset(repo)); + }, + deleteRepo: (repo: Repository, callback: () => void) => { + dispatch(deleteRepo(repo, callback)); } }; }; diff --git a/scm-ui/src/users/components/DeleteUser.js b/scm-ui/src/users/components/DeleteUser.js index 7932e14558..c699d23dd1 100644 --- a/scm-ui/src/users/components/DeleteUser.js +++ b/scm-ui/src/users/components/DeleteUser.js @@ -53,6 +53,7 @@ class DeleteUser extends React.Component<Props> { if (!this.isDeletable()) { return null; } + return ( <> <Subtitle subtitle={t("delete.subtitle")} /> From 7160aa98f9018c506fc3fde984e95f4e11b09a2d Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 23 Jan 2019 12:44:02 +0100 Subject: [PATCH 021/112] removed unnecessary stuff in reporoot --- scm-ui/src/repos/containers/RepositoryRoot.js | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index fcbe5dfee1..5791622b9a 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -1,6 +1,6 @@ //@flow import React from "react"; -import {deleteRepo, fetchRepoByName, getFetchRepoFailure, getRepository, isFetchRepoPending} from "../modules/repos"; +import {fetchRepoByName, getFetchRepoFailure, getRepository, isFetchRepoPending} from "../modules/repos"; import {connect} from "react-redux"; import {Route, Switch} from "react-router-dom"; @@ -32,7 +32,6 @@ type Props = { // dispatch functions fetchRepoByName: (link: string, namespace: string, name: string) => void, - deleteRepo: (repository: Repository, () => void) => void, // context props t: string => string, @@ -58,14 +57,6 @@ class RepositoryRoot extends React.Component<Props> { return this.stripEndingSlash(this.props.match.url); }; - deleted = () => { - this.props.history.push("/repos"); - }; - - delete = (repository: Repository) => { - this.props.deleteRepo(repository, this.deleted); - }; - matches = (route: any) => { const url = this.matchedUrl(); const regex = new RegExp(`${url}(/branches)?/?[^/]*/changesets?.*`); @@ -227,9 +218,6 @@ const mapDispatchToProps = dispatch => { return { fetchRepoByName: (link: string, namespace: string, name: string) => { dispatch(fetchRepoByName(link, namespace, name)); - }, - deleteRepo: (repository: Repository, callback: () => void) => { - dispatch(deleteRepo(repository, callback)); } }; }; From 5f7432c7584b432b1faca527d209b76c0c3f12bc Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 23 Jan 2019 13:20:57 +0100 Subject: [PATCH 022/112] unified langs + added deletegroup component + renamed editgroup to general --- scm-ui/public/locales/en/groups.json | 20 +++++--- scm-ui/public/locales/en/repos.json | 2 +- scm-ui/public/locales/en/users.json | 4 +- .../DeleteGroupNavLink.js => DeleteGroup.js} | 33 +++++++++---- ...oupNavLink.test.js => DeleteGroup.test.js} | 12 ++--- ...GroupNavLink.js => GeneralGroupNavLink.js} | 6 +-- ...nk.test.js => GeneralGroupNavLink.test.js} | 6 +-- .../src/groups/components/navLinks/index.js | 3 +- scm-ui/src/groups/containers/SingleGroup.js | 47 +++++++++---------- scm-ui/src/repos/components/DeleteRepo.js | 20 ++++---- scm-ui/src/users/components/DeleteUser.js | 14 +++--- 11 files changed, 90 insertions(+), 77 deletions(-) rename scm-ui/src/groups/components/{navLinks/DeleteGroupNavLink.js => DeleteGroup.js} (51%) rename scm-ui/src/groups/components/{navLinks/DeleteGroupNavLink.test.js => DeleteGroup.test.js} (83%) rename scm-ui/src/groups/components/navLinks/{EditGroupNavLink.js => GeneralGroupNavLink.js} (69%) rename scm-ui/src/groups/components/navLinks/{editGroupNavLink.test.js => GeneralGroupNavLink.test.js} (66%) diff --git a/scm-ui/public/locales/en/groups.json b/scm-ui/public/locales/en/groups.json index 66075be273..b14d570ee2 100644 --- a/scm-ui/public/locales/en/groups.json +++ b/scm-ui/public/locales/en/groups.json @@ -11,11 +11,16 @@ "title": "Groups", "subtitle": "Create, read, update and delete groups" }, - "single-group": { - "navigationLabel": "Group Navigation", - "informationNavLink": "Information", + "singleGroup": { "errorTitle": "Error", - "errorSubtitle": "Unknown group error" + "errorSubtitle": "Unknown group error", + "menu": { + "navigationLabel": "Group Navigation", + "informationNavLink": "Information", + "settingsNavLink": "Settings", + "editNavLink": "General", + "permissionsNavLink": "Permissions" + } }, "add-group": { "title": "Create Group", @@ -53,9 +58,10 @@ "memberHelpText": "Usernames of the group members" } }, - "delete-group-button": { - "label": "Delete", - "confirm-alert": { + "deleteGroup": { + "subtitle": "Delete Group", + "button": "Delete", + "confirmAlert": { "title": "Delete Group", "message": "Do you really want to delete the group?", "submit": "Yes", diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index a7a7d8449f..564748c9eb 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -118,7 +118,7 @@ "loading": "Loading..." } }, - "delete": { + "deleteRepo": { "subtitle": "Delete Repository", "button": "Delete", "confirmAlert": { diff --git a/scm-ui/public/locales/en/users.json b/scm-ui/public/locales/en/users.json index 394d23dbb0..e7f0661fdf 100644 --- a/scm-ui/public/locales/en/users.json +++ b/scm-ui/public/locales/en/users.json @@ -42,10 +42,10 @@ "title": "Create User", "subtitle": "Create a new user" }, - "delete": { + "deleteUser": { "subtitle": "Delete User", "button": "Delete", - "confirm-alert": { + "confirmAlert": { "title": "Delete user", "message": "Do you really want to delete the user?", "submit": "Yes", diff --git a/scm-ui/src/groups/components/navLinks/DeleteGroupNavLink.js b/scm-ui/src/groups/components/DeleteGroup.js similarity index 51% rename from scm-ui/src/groups/components/navLinks/DeleteGroupNavLink.js rename to scm-ui/src/groups/components/DeleteGroup.js index 45bbdd3026..c32983ff94 100644 --- a/scm-ui/src/groups/components/navLinks/DeleteGroupNavLink.js +++ b/scm-ui/src/groups/components/DeleteGroup.js @@ -2,16 +2,16 @@ import React from "react"; import { translate } from "react-i18next"; import type { Group } from "@scm-manager/ui-types"; -import { NavAction, confirmAlert } from "@scm-manager/ui-components"; +import { Subtitle, DeleteButton, confirmAlert } from "@scm-manager/ui-components"; type Props = { group: Group, confirmDialog?: boolean, - t: string => string, - deleteGroup: (group: Group) => void + deleteGroup: (group: Group) => void, + t: string => string }; -export class DeleteGroupNavLink extends React.Component<Props> { +export class DeleteGroup extends React.Component<Props> { static defaultProps = { confirmDialog: true }; @@ -23,15 +23,15 @@ export class DeleteGroupNavLink extends React.Component<Props> { confirmDelete = () => { const { t } = this.props; confirmAlert({ - title: t("delete-group-button.confirm-alert.title"), - message: t("delete-group-button.confirm-alert.message"), + title: t("deleteGroup.confirmAlert.title"), + message: t("deleteGroup.confirmAlert.message"), buttons: [ { - label: t("delete-group-button.confirm-alert.submit"), + label: t("deleteGroup.confirmAlert.submit"), onClick: () => this.deleteGroup() }, { - label: t("delete-group-button.confirm-alert.cancel"), + label: t("deleteGroup.confirmAlert.cancel"), onClick: () => null } ] @@ -49,8 +49,21 @@ export class DeleteGroupNavLink extends React.Component<Props> { if (!this.isDeletable()) { return null; } - return <NavAction label={t("delete-group-button.label")} action={action} />; + + return ( + <> + <Subtitle subtitle={t("deleteGroup.subtitle")} /> + <div className="columns"> + <div className="column"> + <DeleteButton + label={t("deleteGroup.button")} + action={action} + /> + </div> + </div> + </> + ); } } -export default translate("groups")(DeleteGroupNavLink); +export default translate("groups")(DeleteGroup); diff --git a/scm-ui/src/groups/components/navLinks/DeleteGroupNavLink.test.js b/scm-ui/src/groups/components/DeleteGroup.test.js similarity index 83% rename from scm-ui/src/groups/components/navLinks/DeleteGroupNavLink.test.js rename to scm-ui/src/groups/components/DeleteGroup.test.js index 49f8d95c63..2364a196e8 100644 --- a/scm-ui/src/groups/components/navLinks/DeleteGroupNavLink.test.js +++ b/scm-ui/src/groups/components/DeleteGroup.test.js @@ -1,8 +1,8 @@ import React from "react"; import { mount, shallow } from "enzyme"; -import "../../../tests/enzyme"; +import "../../tests/enzyme"; import "../../../tests/i18n"; -import DeleteGroupNavLink from "./DeleteGroupNavLink"; +import DeleteGroup from "./DeleteGroup"; import { confirmAlert } from "@scm-manager/ui-components"; jest.mock("@scm-manager/ui-components", () => ({ @@ -17,7 +17,7 @@ describe("DeleteGroupNavLink", () => { }; const navLink = shallow( - <DeleteGroupNavLink group={group} deleteGroup={() => {}} /> + <DeleteGroup group={group} deleteGroup={() => {}} /> ); expect(navLink.text()).toBe(""); }); @@ -32,7 +32,7 @@ describe("DeleteGroupNavLink", () => { }; const navLink = mount( - <DeleteGroupNavLink group={group} deleteGroup={() => {}} /> + <DeleteGroup group={group} deleteGroup={() => {}} /> ); expect(navLink.text()).not.toBe(""); }); @@ -47,7 +47,7 @@ describe("DeleteGroupNavLink", () => { }; const navLink = mount( - <DeleteGroupNavLink group={group} deleteGroup={() => {}} /> + <DeleteGroup group={group} deleteGroup={() => {}} /> ); navLink.find("a").simulate("click"); @@ -69,7 +69,7 @@ describe("DeleteGroupNavLink", () => { } const navLink = mount( - <DeleteGroupNavLink + <DeleteGroup group={group} confirmDialog={false} deleteGroup={capture} diff --git a/scm-ui/src/groups/components/navLinks/EditGroupNavLink.js b/scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.js similarity index 69% rename from scm-ui/src/groups/components/navLinks/EditGroupNavLink.js rename to scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.js index a0e36bc8d7..fe59fa41a3 100644 --- a/scm-ui/src/groups/components/navLinks/EditGroupNavLink.js +++ b/scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.js @@ -12,13 +12,13 @@ type Props = { type State = {}; -class EditGroupNavLink extends React.Component<Props, State> { +class GeneralGroupNavLink extends React.Component<Props, State> { render() { const { t, editUrl } = this.props; if (!this.isEditable()) { return null; } - return <NavLink label={t("edit-group-button.label")} to={editUrl} />; + return <NavLink label={t("singleGroup.menu.editNavLink")} to={editUrl} />; } isEditable = () => { @@ -26,4 +26,4 @@ class EditGroupNavLink extends React.Component<Props, State> { }; } -export default translate("groups")(EditGroupNavLink); +export default translate("groups")(GeneralGroupNavLink); diff --git a/scm-ui/src/groups/components/navLinks/editGroupNavLink.test.js b/scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.test.js similarity index 66% rename from scm-ui/src/groups/components/navLinks/editGroupNavLink.test.js rename to scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.test.js index 7399f4f714..1120dab2fd 100644 --- a/scm-ui/src/groups/components/navLinks/editGroupNavLink.test.js +++ b/scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.test.js @@ -4,14 +4,14 @@ import React from "react"; import { shallow } from "enzyme"; import "../../../tests/enzyme"; import "../../../tests/i18n"; -import EditGroupNavLink from "./EditGroupNavLink"; +import GeneralGroupNavLink from "./GeneralGroupNavLink"; it("should render nothing, if the edit link is missing", () => { const group = { _links: {} }; - const navLink = shallow(<EditGroupNavLink group={group} editUrl='/group/edit'/>); + const navLink = shallow(<GeneralGroupNavLink group={group} editUrl='/group/edit'/>); expect(navLink.text()).toBe(""); }); @@ -24,6 +24,6 @@ it("should render the navLink", () => { } }; - const navLink = shallow(<EditGroupNavLink group={group} editUrl='/group/edit'/>); + const navLink = shallow(<GeneralGroupNavLink group={group} editUrl='/group/edit'/>); expect(navLink.text()).not.toBe(""); }); diff --git a/scm-ui/src/groups/components/navLinks/index.js b/scm-ui/src/groups/components/navLinks/index.js index 30fdd34b6d..46dc704d5b 100644 --- a/scm-ui/src/groups/components/navLinks/index.js +++ b/scm-ui/src/groups/components/navLinks/index.js @@ -1,2 +1 @@ -export { default as DeleteGroupNavLink } from "./DeleteGroupNavLink"; -export { default as EditGroupNavLink } from "./EditGroupNavLink"; +export { default as GeneralGroupNavLink } from "./GeneralGroupNavLink"; diff --git a/scm-ui/src/groups/containers/SingleGroup.js b/scm-ui/src/groups/containers/SingleGroup.js index ec62333f6f..5a00b7a965 100644 --- a/scm-ui/src/groups/containers/SingleGroup.js +++ b/scm-ui/src/groups/containers/SingleGroup.js @@ -6,22 +6,22 @@ import { ErrorPage, Loading, Navigation, + SubNavigation, Section, NavLink } from "@scm-manager/ui-components"; import { Route } from "react-router"; import { Details } from "./../components/table"; -import { DeleteGroupNavLink, EditGroupNavLink } from "./../components/navLinks"; +import { + GeneralGroupNavLink +} from "./../components/navLinks"; import type { Group } from "@scm-manager/ui-types"; import type { History } from "history"; import { - deleteGroup, fetchGroupByName, getGroupByName, isFetchGroupPending, - getFetchGroupFailure, - getDeleteGroupFailure, - isDeleteGroupPending + getFetchGroupFailure } from "../modules/groups"; import { translate } from "react-i18next"; @@ -36,7 +36,6 @@ type Props = { groupLink: string, // dispatcher functions - deleteGroup: (group: Group, callback?: () => void) => void, fetchGroupByName: (string, string) => void, // context objects @@ -57,14 +56,6 @@ class SingleGroup extends React.Component<Props> { return url; }; - deleteGroup = (group: Group) => { - this.props.deleteGroup(group, this.groupDeleted); - }; - - groupDeleted = () => { - this.props.history.push("/groups"); - }; - matchedUrl = () => { return this.stripEndingSlash(this.props.match.url); }; @@ -75,8 +66,8 @@ class SingleGroup extends React.Component<Props> { if (error) { return ( <ErrorPage - title={t("single-group.errorTitle")} - subtitle={t("single-group.errorSubtitle")} + title={t("singleGroup.errorTitle")} + subtitle={t("singleGroup.errorSubtitle")} error={error} /> ); @@ -98,18 +89,27 @@ class SingleGroup extends React.Component<Props> { component={() => <Details group={group} />} /> <Route - path={`${url}/edit`} + path={`${url}/settings/general`} exact component={() => <EditGroup group={group} />} /> </div> <div className="column"> <Navigation> - <Section label={t("single-group.navigationLabel")}> + <Section label={t("singleGroup.menu.navigationLabel")}> <NavLink to={`${url}`} - label={t("single-group.informationNavLink")} + label={t("singleGroup.menu.informationNavLink")} /> + <SubNavigation + to={`${url}/settings/general`} + label={t("singleGroup.menu.settingsNavLink")} + > + <GeneralGroupNavLink + group={group} + editUrl={`${url}/settings/general`} + /> + </SubNavigation> </Section> </Navigation> </div> @@ -122,10 +122,8 @@ class SingleGroup extends React.Component<Props> { const mapStateToProps = (state, ownProps) => { const name = ownProps.match.params.name; const group = getGroupByName(state, name); - const loading = - isFetchGroupPending(state, name) || isDeleteGroupPending(state, name); - const error = - getFetchGroupFailure(state, name) || getDeleteGroupFailure(state, name); + const loading = isFetchGroupPending(state, name); + const error = getFetchGroupFailure(state, name); const groupLink = getGroupsLink(state); return { @@ -141,9 +139,6 @@ const mapDispatchToProps = dispatch => { return { fetchGroupByName: (link: string, name: string) => { dispatch(fetchGroupByName(link, name)); - }, - deleteGroup: (group: Group, callback?: () => void) => { - dispatch(deleteGroup(group, callback)); } }; }; diff --git a/scm-ui/src/repos/components/DeleteRepo.js b/scm-ui/src/repos/components/DeleteRepo.js index 57ee868586..3ba7b08d48 100644 --- a/scm-ui/src/repos/components/DeleteRepo.js +++ b/scm-ui/src/repos/components/DeleteRepo.js @@ -1,8 +1,8 @@ //@flow import React from "react"; import { translate } from "react-i18next"; -import { Subtitle, DeleteButton, confirmAlert } from "@scm-manager/ui-components"; import type { Repository } from "@scm-manager/ui-types"; +import { Subtitle, DeleteButton, confirmAlert } from "@scm-manager/ui-components"; type Props = { repository: Repository, @@ -20,22 +20,22 @@ class DeleteRepo extends React.Component<Props> { confirmDialog: true }; - delete = () => { + deleteRepo = () => { this.props.delete(this.props.repository); }; confirmDelete = () => { const { t } = this.props; confirmAlert({ - title: t("delete.confirmAlert.title"), - message: t("delete.confirmAlert.message"), + title: t("deleteRepo.confirmAlert.title"), + message: t("deleteRepo.confirmAlert.message"), buttons: [ { - label: t("delete.confirmAlert.submit"), - onClick: () => this.delete() + label: t("deleteRepo.confirmAlert.submit"), + onClick: () => this.deleteRepo() }, { - label: t("delete.confirmAlert.cancel"), + label: t("deleteRepo.confirmAlert.cancel"), onClick: () => null } ] @@ -48,7 +48,7 @@ class DeleteRepo extends React.Component<Props> { render() { const { confirmDialog, t } = this.props; - const action = confirmDialog ? this.confirmDelete : this.delete(); + const action = confirmDialog ? this.confirmDelete : this.deleteRepo; if (!this.isDeletable()) { return null; @@ -56,11 +56,11 @@ class DeleteRepo extends React.Component<Props> { return ( <> - <Subtitle subtitle={t("delete.subtitle")} /> + <Subtitle subtitle={t("deleteRepo.subtitle")} /> <div className="columns"> <div className="column"> <DeleteButton - label={t("delete.button")} + label={t("deleteRepo.button")} action={action} /> </div> diff --git a/scm-ui/src/users/components/DeleteUser.js b/scm-ui/src/users/components/DeleteUser.js index c699d23dd1..b8523a375e 100644 --- a/scm-ui/src/users/components/DeleteUser.js +++ b/scm-ui/src/users/components/DeleteUser.js @@ -1,8 +1,8 @@ // @flow import React from "react"; import { translate } from "react-i18next"; -import { Subtitle, DeleteButton, confirmAlert } from "@scm-manager/ui-components"; import type { User } from "@scm-manager/ui-types"; +import { Subtitle, DeleteButton, confirmAlert } from "@scm-manager/ui-components"; type Props = { user: User, @@ -27,15 +27,15 @@ class DeleteUser extends React.Component<Props> { confirmDelete = () => { const { t } = this.props; confirmAlert({ - title: t("delete.confirm-alert.title"), - message: t("delete.confirm-alert.message"), + title: t("deleteUser.confirmAlert.title"), + message: t("deleteUser.confirmAlert.message"), buttons: [ { - label: t("delete.confirm-alert.submit"), + label: t("deleteUser.confirmAlert.submit"), onClick: () => this.deleteUser() }, { - label: t("delete.confirm-alert.cancel"), + label: t("deleteUser.confirmAlert.cancel"), onClick: () => null } ] @@ -56,11 +56,11 @@ class DeleteUser extends React.Component<Props> { return ( <> - <Subtitle subtitle={t("delete.subtitle")} /> + <Subtitle subtitle={t("deleteUser.subtitle")} /> <div className="columns"> <div className="column"> <DeleteButton - label={t("delete.button")} + label={t("deleteUser.button")} action={action} /> </div> From 0b4c7148e98c6788fc6f17ee03940501720fa613 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 23 Jan 2019 14:28:57 +0100 Subject: [PATCH 023/112] lang file + fixed edit group subtitle, paths + remaned editgroup to general --- scm-ui/public/locales/en/groups.json | 10 +++--- scm-ui/public/locales/en/users.json | 4 +-- scm-ui/src/groups/components/GroupForm.js | 23 +++++++------ .../{EditGroup.js => GeneralGroup.js} | 32 +++++++++++++++---- scm-ui/src/groups/containers/SingleGroup.js | 8 ++--- 5 files changed, 51 insertions(+), 26 deletions(-) rename scm-ui/src/groups/containers/{EditGroup.js => GeneralGroup.js} (76%) diff --git a/scm-ui/public/locales/en/groups.json b/scm-ui/public/locales/en/groups.json index 51b3c32c4e..654ab2a2db 100644 --- a/scm-ui/public/locales/en/groups.json +++ b/scm-ui/public/locales/en/groups.json @@ -46,12 +46,12 @@ "placeholder": "Enter member", "loading": "Loading...", "no-options": "No suggestion available" - }, - -"group-form": { + }, + "groupForm": { + "subtitle": "Edit Group", "submit": "Submit", - "name-error": "Group name is invalid", - "description-error": "Description is invalid", + "nameError": "Group name is invalid", + "descriptionError": "Description is invalid", "help": { "nameHelpText": "Unique name of the group", "descriptionHelpText": "A short description of the group", diff --git a/scm-ui/public/locales/en/users.json b/scm-ui/public/locales/en/users.json index f151786183..731b59e563 100644 --- a/scm-ui/public/locales/en/users.json +++ b/scm-ui/public/locales/en/users.json @@ -35,8 +35,8 @@ "informationNavLink": "Information", "settingsNavLink": "Settings", "editNavLink": "General", - "setPasswordNavLink": "Set Password", - "setPermissionsNavLink": "Set Permissions" + "setPasswordNavLink": "Password", + "setPermissionsNavLink": "Permissions" } }, "addUser": { diff --git a/scm-ui/src/groups/components/GroupForm.js b/scm-ui/src/groups/components/GroupForm.js index 7cc2ee5d24..75f6e89579 100644 --- a/scm-ui/src/groups/components/GroupForm.js +++ b/scm-ui/src/groups/components/GroupForm.js @@ -2,6 +2,7 @@ import React from "react"; import { translate } from "react-i18next"; import { + Subtitle, AutocompleteAddEntryToTableField, LabelWithHelpIcon, MemberNameTable, @@ -73,34 +74,38 @@ class GroupForm extends React.Component<Props, State> { render() { const { t, loading } = this.props; const { group } = this.state; - let nameField = null; + let firstField = null; if (!this.props.group) { - nameField = ( + // create new group + firstField = ( <InputField label={t("group.name")} - errorMessage={t("group-form.name-error")} + errorMessage={t("groupForm.nameError")} onChange={this.handleGroupNameChange} value={group.name} validationError={this.state.nameValidationError} - helpText={t("group-form.help.nameHelpText")} + helpText={t("groupForm.help.nameHelpText")} /> ); + } else { + // edit existing group + firstField = <Subtitle subtitle={t("groupForm.subtitle")} />; } return ( <form onSubmit={this.submit}> - {nameField} + {firstField} <Textarea label={t("group.description")} - errorMessage={t("group-form.description-error")} + errorMessage={t("groupForm.descriptionError")} onChange={this.handleDescriptionChange} value={group.description} validationError={false} - helpText={t("group-form.help.descriptionHelpText")} + helpText={t("groupForm.help.descriptionHelpText")} /> <LabelWithHelpIcon label={t("group.members")} - helpText={t("group-form.help.memberHelpText")} + helpText={t("groupForm.help.memberHelpText")} /> <MemberNameTable members={group.members} @@ -120,7 +125,7 @@ class GroupForm extends React.Component<Props, State> { /> <SubmitButton disabled={!this.isValid()} - label={t("group-form.submit")} + label={t("groupForm.submit")} loading={loading} /> </form> diff --git a/scm-ui/src/groups/containers/EditGroup.js b/scm-ui/src/groups/containers/GeneralGroup.js similarity index 76% rename from scm-ui/src/groups/containers/EditGroup.js rename to scm-ui/src/groups/containers/GeneralGroup.js index 223ea1eef6..a89491568f 100644 --- a/scm-ui/src/groups/containers/EditGroup.js +++ b/scm-ui/src/groups/containers/GeneralGroup.js @@ -3,9 +3,12 @@ import React from "react"; import { connect } from "react-redux"; import GroupForm from "../components/GroupForm"; import { + modifyGroup, + deleteGroup, getModifyGroupFailure, isModifyGroupPending, - modifyGroup, + getDeleteGroupFailure, + isDeleteGroupPending, modifyGroupReset } from "../modules/groups"; import type { History } from "history"; @@ -13,19 +16,21 @@ import { withRouter } from "react-router-dom"; import type { Group } from "@scm-manager/ui-types"; import { ErrorNotification } from "@scm-manager/ui-components"; import { getUserAutoCompleteLink } from "../../modules/indexResource"; +import DeleteGroup from "../components/DeleteGroup"; type Props = { group: Group, + fetchGroup: (name: string) => void, modifyGroup: (group: Group, callback?: () => void) => void, modifyGroupReset: Group => void, - fetchGroup: (name: string) => void, + deleteGroup: (group: Group, callback?: () => void) => void, autocompleteLink: string, history: History, loading?: boolean, error: Error }; -class EditGroup extends React.Component<Props> { +class GeneralGroup extends React.Component<Props> { componentDidMount() { const { group, modifyGroupReset } = this.props; modifyGroupReset(group); @@ -39,6 +44,14 @@ class EditGroup extends React.Component<Props> { this.props.modifyGroup(group, this.groupModified(group)); }; + deleteGroup = (group: Group) => { + this.props.deleteGroup(group, this.groupDeleted); + }; + + groupDeleted = () => { + this.props.history.push("/groups"); + }; + loadUserAutocompletion = (inputValue: string) => { const url = this.props.autocompleteLink + "?q="; return fetch(url + inputValue) @@ -66,14 +79,18 @@ class EditGroup extends React.Component<Props> { loading={loading} loadUserSuggestions={this.loadUserAutocompletion} /> + <hr /> + <DeleteGroup + group={group} + deleteGroup={this.deleteGroup} /> </div> ); } } const mapStateToProps = (state, ownProps) => { - const loading = isModifyGroupPending(state, ownProps.group.name); - const error = getModifyGroupFailure(state, ownProps.group.name); + const loading = isModifyGroupPending(state, ownProps.group.name) || isDeleteGroupPending(state, ownProps.group.name); + const error = getModifyGroupFailure(state, ownProps.group.name) || getDeleteGroupFailure(state, ownProps.group.name); const autocompleteLink = getUserAutoCompleteLink(state); return { loading, @@ -89,6 +106,9 @@ const mapDispatchToProps = dispatch => { }, modifyGroupReset: (group: Group) => { dispatch(modifyGroupReset(group)); + }, + deleteGroup: (group: Group, callback?: () => void) => { + dispatch(deleteGroup(group, callback)); } }; }; @@ -96,4 +116,4 @@ const mapDispatchToProps = dispatch => { export default connect( mapStateToProps, mapDispatchToProps -)(withRouter(EditGroup)); +)(withRouter(GeneralGroup)); diff --git a/scm-ui/src/groups/containers/SingleGroup.js b/scm-ui/src/groups/containers/SingleGroup.js index 29a188c964..aefe73fe5d 100644 --- a/scm-ui/src/groups/containers/SingleGroup.js +++ b/scm-ui/src/groups/containers/SingleGroup.js @@ -26,7 +26,7 @@ import { } from "../modules/groups"; import { translate } from "react-i18next"; -import EditGroup from "./EditGroup"; +import GeneralGroup from "./GeneralGroup"; import { getGroupsLink } from "../../modules/indexResource"; import SetPermissions from "../../permissions/components/SetPermissions"; import {ExtensionPoint} from "@scm-manager/ui-extensions"; @@ -99,10 +99,10 @@ class SingleGroup extends React.Component<Props> { <Route path={`${url}/settings/general`} exact - component={() => <EditGroup group={group} />} + component={() => <GeneralGroup group={group} />} /> <Route - path={`${url}/permissions`} + path={`${url}/settings/permissions`} exact component={() => ( <SetPermissions selectedPermissionsLink={group._links.permissions} /> @@ -136,7 +136,7 @@ class SingleGroup extends React.Component<Props> { /> <SetPermissionsNavLink group={group} - permissionsUrl={`${url}/permissions`} + permissionsUrl={`${url}/settings/permissions`} /> </SubNavigation> </Section> From 4b24c8db068cce2022fe2d812a96082aec65f93c Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 23 Jan 2019 14:48:29 +0100 Subject: [PATCH 024/112] corrected subtitle in create/edit --- scm-ui/src/groups/components/GroupForm.js | 82 ++++++++++++----------- scm-ui/src/users/components/UserForm.js | 7 +- scm-ui/src/users/containers/SingleUser.js | 2 +- 3 files changed, 50 insertions(+), 41 deletions(-) diff --git a/scm-ui/src/groups/components/GroupForm.js b/scm-ui/src/groups/components/GroupForm.js index 75f6e89579..8693cb9b47 100644 --- a/scm-ui/src/groups/components/GroupForm.js +++ b/scm-ui/src/groups/components/GroupForm.js @@ -72,12 +72,13 @@ class GroupForm extends React.Component<Props, State> { }; render() { - const { t, loading } = this.props; + const { loading, t } = this.props; const { group } = this.state; - let firstField = null; + let nameField = null; + let subtitle = null; if (!this.props.group) { // create new group - firstField = ( + nameField = ( <InputField label={t("group.name")} errorMessage={t("groupForm.nameError")} @@ -89,46 +90,49 @@ class GroupForm extends React.Component<Props, State> { ); } else { // edit existing group - firstField = <Subtitle subtitle={t("groupForm.subtitle")} />; + subtitle = <Subtitle subtitle={t("groupForm.subtitle")} />; } return ( - <form onSubmit={this.submit}> - {firstField} - <Textarea - label={t("group.description")} - errorMessage={t("groupForm.descriptionError")} - onChange={this.handleDescriptionChange} - value={group.description} - validationError={false} - helpText={t("groupForm.help.descriptionHelpText")} - /> - <LabelWithHelpIcon - label={t("group.members")} - helpText={t("groupForm.help.memberHelpText")} - /> - <MemberNameTable - members={group.members} - memberListChanged={this.memberListChanged} - /> + <> + {subtitle} + <form onSubmit={this.submit}> + {nameField} + <Textarea + label={t("group.description")} + errorMessage={t("groupForm.descriptionError")} + onChange={this.handleDescriptionChange} + value={group.description} + validationError={false} + helpText={t("groupForm.help.descriptionHelpText")} + /> + <LabelWithHelpIcon + label={t("group.members")} + helpText={t("groupForm.help.memberHelpText")} + /> + <MemberNameTable + members={group.members} + memberListChanged={this.memberListChanged} + /> - <AutocompleteAddEntryToTableField - addEntry={this.addMember} - disabled={false} - buttonLabel={t("add-member-button.label")} - fieldLabel={t("add-member-textfield.label")} - errorMessage={t("add-member-textfield.error")} - loadSuggestions={this.props.loadUserSuggestions} - placeholder={t("add-member-autocomplete.placeholder")} - loadingMessage={t("add-member-autocomplete.loading")} - noOptionsMessage={t("add-member-autocomplete.no-options")} - /> - <SubmitButton - disabled={!this.isValid()} - label={t("groupForm.submit")} - loading={loading} - /> - </form> + <AutocompleteAddEntryToTableField + addEntry={this.addMember} + disabled={false} + buttonLabel={t("add-member-button.label")} + fieldLabel={t("add-member-textfield.label")} + errorMessage={t("add-member-textfield.error")} + loadSuggestions={this.props.loadUserSuggestions} + placeholder={t("add-member-autocomplete.placeholder")} + loadingMessage={t("add-member-autocomplete.loading")} + noOptionsMessage={t("add-member-autocomplete.no-options")} + /> + <SubmitButton + disabled={!this.isValid()} + label={t("groupForm.submit")} + loading={loading} + /> + </form> + </> ); } diff --git a/scm-ui/src/users/components/UserForm.js b/scm-ui/src/users/components/UserForm.js index d01838f08a..bb368666d9 100644 --- a/scm-ui/src/users/components/UserForm.js +++ b/scm-ui/src/users/components/UserForm.js @@ -88,7 +88,9 @@ class UserForm extends React.Component<Props, State> { let nameField = null; let passwordChangeField = null; + let subtitle = null; if (!this.props.user) { + // create new user nameField = ( <InputField label={t("user.name")} @@ -103,10 +105,13 @@ class UserForm extends React.Component<Props, State> { passwordChangeField = ( <PasswordConfirmation passwordChanged={this.handlePasswordChange} /> ); + } else { + // edit existing user + subtitle = <Subtitle subtitle={t("userForm.subtitle")} />; } return ( <> - <Subtitle subtitle={t("userForm.subtitle")} /> + {subtitle} <form onSubmit={this.submit}> <div className="columns"> <div className="column is-half"> diff --git a/scm-ui/src/users/containers/SingleUser.js b/scm-ui/src/users/containers/SingleUser.js index 734d7cb209..ad95aa81ec 100644 --- a/scm-ui/src/users/containers/SingleUser.js +++ b/scm-ui/src/users/containers/SingleUser.js @@ -92,7 +92,7 @@ class SingleUser extends React.Component<Props> { component={() => <SetUserPassword user={user} />} /> <Route - path={`${url}/permissions`} + path={`${url}/settings/permissions`} component={() => ( <SetPermissions selectedPermissionsLink={user._links.permissions} From b90588a6f466e1f9c1ebcd5376a53ff9a4bfee7c Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 23 Jan 2019 15:08:28 +0100 Subject: [PATCH 025/112] renamed lang + component --- scm-ui/public/locales/en/groups.json | 2 +- scm-ui/public/locales/en/repos.json | 2 +- scm-ui/public/locales/en/users.json | 2 +- .../groups/components/navLinks/GeneralGroupNavLink.js | 2 +- .../{EditNavLink.js => GeneralRepoNavLink.js} | 6 +++--- ...{EditNavLink.test.js => GeneralRepoNavLink.test.js} | 10 +++++----- scm-ui/src/repos/containers/RepositoryRoot.js | 3 ++- .../users/components/navLinks/GeneralUserNavLink.js | 2 +- 8 files changed, 15 insertions(+), 14 deletions(-) rename scm-ui/src/repos/components/{EditNavLink.js => GeneralRepoNavLink.js} (69%) rename scm-ui/src/repos/components/{EditNavLink.test.js => GeneralRepoNavLink.test.js} (70%) diff --git a/scm-ui/public/locales/en/groups.json b/scm-ui/public/locales/en/groups.json index 654ab2a2db..83dcf18fe5 100644 --- a/scm-ui/public/locales/en/groups.json +++ b/scm-ui/public/locales/en/groups.json @@ -18,7 +18,7 @@ "navigationLabel": "Group Navigation", "informationNavLink": "Information", "settingsNavLink": "Settings", - "editNavLink": "General", + "generalNavLink": "General", "setPermissionsNavLink": "Permissions" } }, diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 564748c9eb..577386572e 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -26,7 +26,7 @@ "historyNavLink": "Commits", "sourcesNavLink": "Sources", "settingsNavLink": "Settings", - "editNavLink": "General", + "generalNavLink": "General", "permissionsNavLink": "Permissions" } }, diff --git a/scm-ui/public/locales/en/users.json b/scm-ui/public/locales/en/users.json index 731b59e563..e907f88f4c 100644 --- a/scm-ui/public/locales/en/users.json +++ b/scm-ui/public/locales/en/users.json @@ -34,7 +34,7 @@ "navigationLabel": "User Navigation", "informationNavLink": "Information", "settingsNavLink": "Settings", - "editNavLink": "General", + "generalNavLink": "General", "setPasswordNavLink": "Password", "setPermissionsNavLink": "Permissions" } diff --git a/scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.js b/scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.js index fe59fa41a3..5b0528ba3a 100644 --- a/scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.js +++ b/scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.js @@ -18,7 +18,7 @@ class GeneralGroupNavLink extends React.Component<Props, State> { if (!this.isEditable()) { return null; } - return <NavLink label={t("singleGroup.menu.editNavLink")} to={editUrl} />; + return <NavLink label={t("singleGroup.menu.generalNavLink")} to={editUrl} />; } isEditable = () => { diff --git a/scm-ui/src/repos/components/EditNavLink.js b/scm-ui/src/repos/components/GeneralRepoNavLink.js similarity index 69% rename from scm-ui/src/repos/components/EditNavLink.js rename to scm-ui/src/repos/components/GeneralRepoNavLink.js index b06cd8d96e..a76231529b 100644 --- a/scm-ui/src/repos/components/EditNavLink.js +++ b/scm-ui/src/repos/components/GeneralRepoNavLink.js @@ -6,7 +6,7 @@ import type { Repository } from "@scm-manager/ui-types"; type Props = { editUrl: string, t: string => string, repository: Repository }; -class EditNavLink extends React.Component<Props> { +class GeneralRepoNavLink extends React.Component<Props> { isEditable = () => { return this.props.repository._links.update; }; @@ -15,8 +15,8 @@ class EditNavLink extends React.Component<Props> { return null; } const { editUrl, t } = this.props; - return <NavLink to={editUrl} label={t("repositoryRoot.menu.editNavLink")} />; + return <NavLink to={editUrl} label={t("repositoryRoot.menu.generalNavLink")} />; } } -export default translate("repos")(EditNavLink); +export default translate("repos")(GeneralRepoNavLink); diff --git a/scm-ui/src/repos/components/EditNavLink.test.js b/scm-ui/src/repos/components/GeneralRepoNavLink.test.js similarity index 70% rename from scm-ui/src/repos/components/EditNavLink.test.js rename to scm-ui/src/repos/components/GeneralRepoNavLink.test.js index 080440cc88..2ce050f69c 100644 --- a/scm-ui/src/repos/components/EditNavLink.test.js +++ b/scm-ui/src/repos/components/GeneralRepoNavLink.test.js @@ -3,9 +3,9 @@ import { shallow, mount } from "enzyme"; import "../../tests/enzyme"; import "../../tests/i18n"; import ReactRouterEnzymeContext from "react-router-enzyme-context"; -import EditNavLink from "./EditNavLink"; +import GeneralRepoNavLink from "./GeneralRepoNavLink"; -describe("EditNavLink", () => { +describe("GeneralNavLink", () => { const options = new ReactRouterEnzymeContext(); it("should render nothing, if the modify link is missing", () => { @@ -14,7 +14,7 @@ describe("EditNavLink", () => { }; const navLink = shallow( - <EditNavLink repository={repository} editUrl="" />, + <GeneralRepoNavLink repository={repository} editUrl="" />, options.get() ); expect(navLink.text()).toBe(""); @@ -30,9 +30,9 @@ describe("EditNavLink", () => { }; const navLink = mount( - <EditNavLink repository={repository} editUrl="" />, + <GeneralRepoNavLink repository={repository} editUrl="" />, options.get() ); - expect(navLink.text()).toBe("repositoryRoot.menu.editNavLink"); + expect(navLink.text()).toBe("repositoryRoot.menu.generalNavLink"); }); }); diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 5791622b9a..dcad647d4e 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -13,6 +13,7 @@ import GeneralRepo from "./GeneralRepo"; import Permissions from "../permissions/containers/Permissions"; import type {History} from "history"; +import GeneralRepoNavLink from "../components/GeneralRepoNavLink"; import BranchRoot from "./ChangesetsRoot"; import ChangesetView from "./ChangesetView"; @@ -183,7 +184,7 @@ class RepositoryRoot extends React.Component<Props> { to={`${url}/settings/general`} label={t("repositoryRoot.menu.settingsNavLink")} > - <NavLink repository={repository} editUrl={`${url}/settings/general`} /> + <GeneralRepoNavLink repository={repository} editUrl={`${url}/settings/general`} /> <PermissionsNavLink permissionUrl={`${url}/settings/permissions`} repository={repository} diff --git a/scm-ui/src/users/components/navLinks/GeneralUserNavLink.js b/scm-ui/src/users/components/navLinks/GeneralUserNavLink.js index d4bce5b8b5..417c9c76f6 100644 --- a/scm-ui/src/users/components/navLinks/GeneralUserNavLink.js +++ b/scm-ui/src/users/components/navLinks/GeneralUserNavLink.js @@ -17,7 +17,7 @@ class GeneralUserNavLink extends React.Component<Props> { if (!this.isEditable()) { return null; } - return <NavLink label={t("singleUser.menu.editNavLink")} to={editUrl} />; + return <NavLink label={t("singleUser.menu.generalNavLink")} to={editUrl} />; } isEditable = () => { From 2e3fcbd86029dbf88202c4199cc62f432b5cbdcf Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 23 Jan 2019 15:16:36 +0100 Subject: [PATCH 026/112] unified navlink components --- .../navLinks/GeneralGroupNavLink.js | 27 +++++++++---------- .../repos/components/GeneralRepoNavLink.js | 12 ++++++--- .../components/navLinks/GeneralUserNavLink.js | 16 +++++------ 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.js b/scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.js index 5b0528ba3a..b04e572937 100644 --- a/scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.js +++ b/scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.js @@ -1,29 +1,28 @@ //@flow import React from "react"; +import type { Group } from "@scm-manager/ui-types"; import { NavLink } from "@scm-manager/ui-components"; import { translate } from "react-i18next"; -import type { Group } from "@scm-manager/ui-types"; type Props = { - t: string => string, + group: Group, editUrl: string, - group: Group + t: string => string }; -type State = {}; - -class GeneralGroupNavLink extends React.Component<Props, State> { - render() { - const { t, editUrl } = this.props; - if (!this.isEditable()) { - return null; - } - return <NavLink label={t("singleGroup.menu.generalNavLink")} to={editUrl} />; - } - +class GeneralGroupNavLink extends React.Component<Props> { isEditable = () => { return this.props.group._links.update; }; + + render() { + const { t, editUrl } = this.props; + + if (!this.isEditable()) { + return null; + } + return <NavLink to={editUrl} label={t("singleGroup.menu.generalNavLink")} />; + } } export default translate("groups")(GeneralGroupNavLink); diff --git a/scm-ui/src/repos/components/GeneralRepoNavLink.js b/scm-ui/src/repos/components/GeneralRepoNavLink.js index a76231529b..650cf0f62c 100644 --- a/scm-ui/src/repos/components/GeneralRepoNavLink.js +++ b/scm-ui/src/repos/components/GeneralRepoNavLink.js @@ -1,20 +1,26 @@ //@flow import React from "react"; +import type { Repository } from "@scm-manager/ui-types"; import { NavLink } from "@scm-manager/ui-components"; import { translate } from "react-i18next"; -import type { Repository } from "@scm-manager/ui-types"; -type Props = { editUrl: string, t: string => string, repository: Repository }; +type Props = { + repository: Repository, + editUrl: string, + t: string => string +}; class GeneralRepoNavLink extends React.Component<Props> { isEditable = () => { return this.props.repository._links.update; }; + render() { + const { editUrl, t } = this.props; + if (!this.isEditable()) { return null; } - const { editUrl, t } = this.props; return <NavLink to={editUrl} label={t("repositoryRoot.menu.generalNavLink")} />; } } diff --git a/scm-ui/src/users/components/navLinks/GeneralUserNavLink.js b/scm-ui/src/users/components/navLinks/GeneralUserNavLink.js index 417c9c76f6..e222a7bec2 100644 --- a/scm-ui/src/users/components/navLinks/GeneralUserNavLink.js +++ b/scm-ui/src/users/components/navLinks/GeneralUserNavLink.js @@ -1,28 +1,28 @@ //@flow import React from "react"; -import { translate } from "react-i18next"; import type { User } from "@scm-manager/ui-types"; import { NavLink } from "@scm-manager/ui-components"; +import { translate } from "react-i18next"; type Props = { - t: string => string, user: User, - editUrl: String + editUrl: String, + t: string => string }; class GeneralUserNavLink extends React.Component<Props> { + isEditable = () => { + return this.props.user._links.update; + }; + render() { const { t, editUrl } = this.props; if (!this.isEditable()) { return null; } - return <NavLink label={t("singleUser.menu.generalNavLink")} to={editUrl} />; + return <NavLink to={editUrl} label={t("singleUser.menu.generalNavLink")} />; } - - isEditable = () => { - return this.props.user._links.update; - }; } export default translate("users")(GeneralUserNavLink); From 7d0570e7eed36361d57b53b7dc9410d147cbd1c8 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 23 Jan 2019 15:42:15 +0100 Subject: [PATCH 027/112] added extensionpoints + rehang gitplugin config --- .../src/config/ConfigurationBinder.js | 2 +- scm-ui/public/locales/en/repos.json | 1 + scm-ui/src/containers/Profile.js | 11 ++++ scm-ui/src/groups/containers/SingleGroup.js | 1 + .../repos/components/form/RepositoryForm.js | 55 +++++++++++-------- scm-ui/src/repos/containers/RepositoryRoot.js | 46 ++++++++++++---- scm-ui/src/users/containers/SingleUser.js | 11 ++++ 7 files changed, 92 insertions(+), 35 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js b/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js index 1b2b37bb19..146bbd371f 100644 --- a/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js +++ b/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js @@ -59,7 +59,7 @@ class ConfigurationBinder { }); // bind navigation link to extension point - binder.bind("repository.navigation", RepoNavLink, repoPredicate); + binder.bind("repository.subnavigation", RepoNavLink, repoPredicate); // route for global configuration, passes the current repository to component diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 577386572e..510c64cfb7 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -57,6 +57,7 @@ } }, "repositoryForm": { + "subtitle": "Edit Repository", "submit": "Save" }, "sources": { diff --git a/scm-ui/src/containers/Profile.js b/scm-ui/src/containers/Profile.js index e603a565e8..40392d151e 100644 --- a/scm-ui/src/containers/Profile.js +++ b/scm-ui/src/containers/Profile.js @@ -18,6 +18,7 @@ import { } from "@scm-manager/ui-components"; import ChangeUserPassword from "./ChangeUserPassword"; import ProfileInfo from "./ProfileInfo"; +import {ExtensionPoint} from "@scm-manager/ui-extensions"; type Props = { me: Me, @@ -58,6 +59,11 @@ class Profile extends React.Component<Props, State> { ); } + const extensionProps = { + me, + url + }; + return ( <Page title={me.displayName}> <div className="columns"> @@ -83,6 +89,11 @@ class Profile extends React.Component<Props, State> { to={`${url}/settings/password`} label={t("profile.changePasswordNavLink")} /> + <ExtensionPoint + name="profile.subnavigation" + props={extensionProps} + renderAll={true} + /> </SubNavigation> </Section> </Navigation> diff --git a/scm-ui/src/groups/containers/SingleGroup.js b/scm-ui/src/groups/containers/SingleGroup.js index aefe73fe5d..72d6946220 100644 --- a/scm-ui/src/groups/containers/SingleGroup.js +++ b/scm-ui/src/groups/containers/SingleGroup.js @@ -138,6 +138,7 @@ class SingleGroup extends React.Component<Props> { group={group} permissionsUrl={`${url}/settings/permissions`} /> + <ExtensionPoint name="group.subnavigation" props={extensionProps} renderAll={true} /> </SubNavigation> </Section> </Navigation> diff --git a/scm-ui/src/repos/components/form/RepositoryForm.js b/scm-ui/src/repos/components/form/RepositoryForm.js index a97c76af8b..fcd88f0417 100644 --- a/scm-ui/src/repos/components/form/RepositoryForm.js +++ b/scm-ui/src/repos/components/form/RepositoryForm.js @@ -82,30 +82,39 @@ class RepositoryForm extends React.Component<Props, State> { const { loading, t } = this.props; const repository = this.state.repository; - return ( - <form onSubmit={this.submit}> - {this.renderCreateOnlyFields()} - <InputField - label={t("repository.contact")} - onChange={this.handleContactChange} - value={repository ? repository.contact : ""} - validationError={this.state.contactValidationError} - errorMessage={t("validation.contact-invalid")} - helpText={t("help.contactHelpText")} - /> + let subtitle = null; + if (this.props.repository) { + // edit existing repo + subtitle = <Subtitle subtitle={t("repositoryForm.subtitle")} />; + } - <Textarea - label={t("repository.description")} - onChange={this.handleDescriptionChange} - value={repository ? repository.description : ""} - helpText={t("help.descriptionHelpText")} - /> - <SubmitButton - disabled={!this.isValid()} - loading={loading} - label={t("repositoryForm.submit")} - /> - </form> + return ( + <> + {subtitle} + <form onSubmit={this.submit}> + {this.renderCreateOnlyFields()} + <InputField + label={t("repository.contact")} + onChange={this.handleContactChange} + value={repository ? repository.contact : ""} + validationError={this.state.contactValidationError} + errorMessage={t("validation.contact-invalid")} + helpText={t("help.contactHelpText")} + /> + + <Textarea + label={t("repository.description")} + onChange={this.handleDescriptionChange} + value={repository ? repository.description : ""} + helpText={t("help.descriptionHelpText")} + /> + <SubmitButton + disabled={!this.isValid()} + loading={loading} + label={t("repositoryForm.submit")} + /> + </form> + </> ); } diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index dcad647d4e..52c0a7ed83 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -1,18 +1,31 @@ //@flow import React from "react"; -import {fetchRepoByName, getFetchRepoFailure, getRepository, isFetchRepoPending} from "../modules/repos"; +import { + fetchRepoByName, + getFetchRepoFailure, + getRepository, + isFetchRepoPending +} from "../modules/repos"; -import {connect} from "react-redux"; -import {Route, Switch} from "react-router-dom"; -import type {Repository} from "@scm-manager/ui-types"; +import { connect } from "react-redux"; +import { Route, Switch } from "react-router-dom"; +import type { Repository } from "@scm-manager/ui-types"; -import {ErrorPage, Loading, Navigation, SubNavigation, NavLink, Page, Section} from "@scm-manager/ui-components"; -import {translate} from "react-i18next"; +import { + ErrorPage, + Loading, + Navigation, + SubNavigation, + NavLink, + Page, + Section +} from "@scm-manager/ui-components"; +import { translate } from "react-i18next"; import RepositoryDetails from "../components/RepositoryDetails"; import GeneralRepo from "./GeneralRepo"; import Permissions from "../permissions/containers/Permissions"; -import type {History} from "history"; +import type { History } from "history"; import GeneralRepoNavLink from "../components/GeneralRepoNavLink"; import BranchRoot from "./ChangesetsRoot"; @@ -20,8 +33,8 @@ import ChangesetView from "./ChangesetView"; import PermissionsNavLink from "../components/PermissionsNavLink"; import Sources from "../sources/containers/Sources"; import RepositoryNavLink from "../components/RepositoryNavLink"; -import {getRepositoriesLink} from "../../modules/indexResource"; -import {ExtensionPoint} from "@scm-manager/ui-extensions"; +import { getRepositoriesLink } from "../../modules/indexResource"; +import { ExtensionPoint } from "@scm-manager/ui-extensions"; type Props = { namespace: string, @@ -159,7 +172,10 @@ class RepositoryRoot extends React.Component<Props> { <div className="column"> <Navigation> <Section label={t("repositoryRoot.menu.navigationLabel")}> - <NavLink to={url} label={t("repositoryRoot.menu.informationNavLink")} /> + <NavLink + to={url} + label={t("repositoryRoot.menu.informationNavLink")} + /> <RepositoryNavLink repository={repository} linkName="changesets" @@ -184,11 +200,19 @@ class RepositoryRoot extends React.Component<Props> { to={`${url}/settings/general`} label={t("repositoryRoot.menu.settingsNavLink")} > - <GeneralRepoNavLink repository={repository} editUrl={`${url}/settings/general`} /> + <GeneralRepoNavLink + repository={repository} + editUrl={`${url}/settings/general`} + /> <PermissionsNavLink permissionUrl={`${url}/settings/permissions`} repository={repository} /> + <ExtensionPoint + name="repository.subnavigation" + props={extensionProps} + renderAll={true} + /> </SubNavigation> </Section> </Navigation> diff --git a/scm-ui/src/users/containers/SingleUser.js b/scm-ui/src/users/containers/SingleUser.js index ad95aa81ec..188de0aaee 100644 --- a/scm-ui/src/users/containers/SingleUser.js +++ b/scm-ui/src/users/containers/SingleUser.js @@ -26,6 +26,7 @@ import { translate } from "react-i18next"; import { getUsersLink } from "../../modules/indexResource"; import SetUserPassword from "../components/SetUserPassword"; import SetPermissions from "../../permissions/components/SetPermissions"; +import {ExtensionPoint} from "@scm-manager/ui-extensions"; type Props = { name: string, @@ -78,6 +79,11 @@ class SingleUser extends React.Component<Props> { const url = this.matchedUrl(); + const extensionProps = { + user, + url + }; + return ( <Page title={user.displayName}> <div className="columns"> @@ -123,6 +129,11 @@ class SingleUser extends React.Component<Props> { user={user} permissionsUrl={`${url}/settings/permissions`} /> + <ExtensionPoint + name="user.subnavigation" + props={extensionProps} + renderAll={true} + /> </SubNavigation> </Section> </Navigation> From f8bb3e0b2b985253cbaee76164373963c0c78a71 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 23 Jan 2019 16:27:39 +0100 Subject: [PATCH 028/112] fixed tests --- .../src/groups/components/DeleteGroup.test.js | 22 +++++++++++++------ .../src/repos/components/DeleteRepo.test.js | 20 ++++++++++++----- .../components/PermissionsNavLink.test.js | 2 +- .../src/users/components/DeleteUser.test.js | 22 +++++++++++++------ 4 files changed, 45 insertions(+), 21 deletions(-) diff --git a/scm-ui/src/groups/components/DeleteGroup.test.js b/scm-ui/src/groups/components/DeleteGroup.test.js index 2364a196e8..043724d5f7 100644 --- a/scm-ui/src/groups/components/DeleteGroup.test.js +++ b/scm-ui/src/groups/components/DeleteGroup.test.js @@ -1,15 +1,20 @@ import React from "react"; import { mount, shallow } from "enzyme"; +import ReactRouterEnzymeContext from "react-router-enzyme-context"; + import "../../tests/enzyme"; -import "../../../tests/i18n"; +import "../../tests/i18n"; import DeleteGroup from "./DeleteGroup"; import { confirmAlert } from "@scm-manager/ui-components"; jest.mock("@scm-manager/ui-components", () => ({ confirmAlert: jest.fn(), - NavAction: require.requireActual("@scm-manager/ui-components").NavAction + Subtitle: require.requireActual("@scm-manager/ui-components").Subtitle, + DeleteButton: require.requireActual("@scm-manager/ui-components").DeleteButton })); +const options = new ReactRouterEnzymeContext(); + describe("DeleteGroupNavLink", () => { it("should render nothing, if the delete link is missing", () => { const group = { @@ -32,7 +37,8 @@ describe("DeleteGroupNavLink", () => { }; const navLink = mount( - <DeleteGroup group={group} deleteGroup={() => {}} /> + <DeleteGroup group={group} deleteGroup={() => {}} />, + options.get() ); expect(navLink.text()).not.toBe(""); }); @@ -47,9 +53,10 @@ describe("DeleteGroupNavLink", () => { }; const navLink = mount( - <DeleteGroup group={group} deleteGroup={() => {}} /> + <DeleteGroup group={group} deleteGroup={() => {}} />, + options.get() ); - navLink.find("a").simulate("click"); + navLink.find("button").simulate("click"); expect(confirmAlert.mock.calls.length).toBe(1); }); @@ -73,9 +80,10 @@ describe("DeleteGroupNavLink", () => { group={group} confirmDialog={false} deleteGroup={capture} - /> + />, + options.get() ); - navLink.find("a").simulate("click"); + navLink.find("button").simulate("click"); expect(calledUrl).toBe("/groups"); }); diff --git a/scm-ui/src/repos/components/DeleteRepo.test.js b/scm-ui/src/repos/components/DeleteRepo.test.js index 7985e02ef2..136cec6c03 100644 --- a/scm-ui/src/repos/components/DeleteRepo.test.js +++ b/scm-ui/src/repos/components/DeleteRepo.test.js @@ -1,5 +1,7 @@ import React from "react"; import { mount, shallow } from "enzyme"; +import ReactRouterEnzymeContext from "react-router-enzyme-context"; + import "../../tests/enzyme"; import "../../tests/i18n"; import DeleteRepo from "./DeleteRepo"; @@ -7,9 +9,12 @@ import DeleteRepo from "./DeleteRepo"; import { confirmAlert } from "@scm-manager/ui-components"; jest.mock("@scm-manager/ui-components", () => ({ confirmAlert: jest.fn(), - NavAction: require.requireActual("@scm-manager/ui-components").NavAction + Subtitle: require.requireActual("@scm-manager/ui-components").Subtitle, + DeleteButton: require.requireActual("@scm-manager/ui-components").DeleteButton })); +const options = new ReactRouterEnzymeContext(); + describe("DeleteRepo", () => { it("should render nothing, if the delete link is missing", () => { const repository = { @@ -32,7 +37,8 @@ describe("DeleteRepo", () => { }; const navLink = mount( - <DeleteRepo repository={repository} delete={() => {}} /> + <DeleteRepo repository={repository} delete={() => {}} />, + options.get() ); expect(navLink.text()).not.toBe(""); }); @@ -47,9 +53,10 @@ describe("DeleteRepo", () => { }; const navLink = mount( - <DeleteRepo repository={repository} delete={() => {}} /> + <DeleteRepo repository={repository} delete={() => {}} />, + options.get() ); - navLink.find("a").simulate("click"); + navLink.find("button").simulate("click"); expect(confirmAlert.mock.calls.length).toBe(1); }); @@ -73,9 +80,10 @@ describe("DeleteRepo", () => { repository={repository} confirmDialog={false} delete={capture} - /> + />, + options.get() ); - navLink.find("a").simulate("click"); + navLink.find("button").simulate("click"); expect(calledUrl).toBe("/repos"); }); diff --git a/scm-ui/src/repos/components/PermissionsNavLink.test.js b/scm-ui/src/repos/components/PermissionsNavLink.test.js index 5dddfe0cf4..3f6a95fe7d 100644 --- a/scm-ui/src/repos/components/PermissionsNavLink.test.js +++ b/scm-ui/src/repos/components/PermissionsNavLink.test.js @@ -33,6 +33,6 @@ describe("PermissionsNavLink", () => { <PermissionsNavLink repository={repository} permissionUrl="" />, options.get() ); - expect(navLink.text()).toBe("repository-root.menu.permissions"); + expect(navLink.text()).toBe("repositoryRoot.menu.permissionsNavLink"); }); }); diff --git a/scm-ui/src/users/components/DeleteUser.test.js b/scm-ui/src/users/components/DeleteUser.test.js index e03aad5b49..312efe51d9 100644 --- a/scm-ui/src/users/components/DeleteUser.test.js +++ b/scm-ui/src/users/components/DeleteUser.test.js @@ -1,15 +1,20 @@ import React from "react"; import { mount, shallow } from "enzyme"; +import ReactRouterEnzymeContext from "react-router-enzyme-context"; + import "../../tests/enzyme"; -import "../../../tests/i18n"; +import "../../tests/i18n"; import DeleteUser from "./DeleteUser"; import { confirmAlert } from "@scm-manager/ui-components"; jest.mock("@scm-manager/ui-components", () => ({ confirmAlert: jest.fn(), - NavAction: require.requireActual("@scm-manager/ui-components").NavAction + Subtitle: require.requireActual("@scm-manager/ui-components").Subtitle, + DeleteButton: require.requireActual("@scm-manager/ui-components").DeleteButton })); +const options = new ReactRouterEnzymeContext(); + describe("DeleteUser", () => { it("should render nothing, if the delete link is missing", () => { const user = { @@ -32,7 +37,8 @@ describe("DeleteUser", () => { }; const navLink = mount( - <DeleteUser user={user} deleteUser={() => {}} /> + <DeleteUser user={user} deleteUser={() => {}} />, + options.get() ); expect(navLink.text()).not.toBe(""); }); @@ -47,9 +53,10 @@ describe("DeleteUser", () => { }; const navLink = mount( - <DeleteUser user={user} deleteUser={() => {}} /> + <DeleteUser user={user} deleteUser={() => {}} />, + options.get() ); - navLink.find("a").simulate("click"); + navLink.find("button").simulate("click"); expect(confirmAlert.mock.calls.length).toBe(1); }); @@ -73,9 +80,10 @@ describe("DeleteUser", () => { user={user} confirmDialog={false} deleteUser={capture} - /> + />, + options.get() ); - navLink.find("a").simulate("click"); + navLink.find("button").simulate("click"); expect(calledUrl).toBe("/users"); }); From 27f02c7e8773c267a6ab53f89523d5b2bfd99ed0 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 23 Jan 2019 17:12:10 +0100 Subject: [PATCH 029/112] added default icon for subnavigation and removed icons from subnavigation navlinks --- .../ui-components/src/navigation/SubNavigation.js | 9 ++++++++- .../groups/components/navLinks/GeneralGroupNavLink.js | 2 +- scm-ui/src/repos/components/GeneralRepoNavLink.js | 2 +- scm-ui/src/repos/components/PermissionsNavLink.js | 2 +- .../src/users/components/navLinks/GeneralUserNavLink.js | 2 +- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/navigation/SubNavigation.js b/scm-ui-components/packages/ui-components/src/navigation/SubNavigation.js index 0eef244f59..7acfd93841 100644 --- a/scm-ui-components/packages/ui-components/src/navigation/SubNavigation.js +++ b/scm-ui-components/packages/ui-components/src/navigation/SubNavigation.js @@ -4,6 +4,7 @@ import {Link, Route} from "react-router-dom"; type Props = { to: string, + icon?: string, label: string, activeOnlyWhenExact?: boolean, activeWhenMatch?: (route: any) => boolean, @@ -21,7 +22,12 @@ class SubNavigation extends React.Component<Props> { } renderLink = (route: any) => { - const { to, label } = this.props; + const { to, icon, label } = this.props; + + let defaultIcon = "fas fa-cog"; + if (icon) { + defaultIcon = icon; + } let children = null; if(this.isActive(route)) { @@ -33,6 +39,7 @@ class SubNavigation extends React.Component<Props> { return ( <li> <Link className={this.isActive(route) ? "is-active" : ""} to={to}> + <><i className={defaultIcon}></i>{" "}</> {label} </Link> {children} diff --git a/scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.js b/scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.js index 291742cf25..b04e572937 100644 --- a/scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.js +++ b/scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.js @@ -21,7 +21,7 @@ class GeneralGroupNavLink extends React.Component<Props> { if (!this.isEditable()) { return null; } - return <NavLink to={editUrl} icon="fas fa-cog" label={t("singleGroup.menu.generalNavLink")} />; + return <NavLink to={editUrl} label={t("singleGroup.menu.generalNavLink")} />; } } diff --git a/scm-ui/src/repos/components/GeneralRepoNavLink.js b/scm-ui/src/repos/components/GeneralRepoNavLink.js index 314c86c6ff..650cf0f62c 100644 --- a/scm-ui/src/repos/components/GeneralRepoNavLink.js +++ b/scm-ui/src/repos/components/GeneralRepoNavLink.js @@ -21,7 +21,7 @@ class GeneralRepoNavLink extends React.Component<Props> { if (!this.isEditable()) { return null; } - return <NavLink to={editUrl} icon="fas fa-cog" label={t("repositoryRoot.menu.generalNavLink")} />; + return <NavLink to={editUrl} label={t("repositoryRoot.menu.generalNavLink")} />; } } diff --git a/scm-ui/src/repos/components/PermissionsNavLink.js b/scm-ui/src/repos/components/PermissionsNavLink.js index 1d6d52eb0b..773ad94246 100644 --- a/scm-ui/src/repos/components/PermissionsNavLink.js +++ b/scm-ui/src/repos/components/PermissionsNavLink.js @@ -20,7 +20,7 @@ class PermissionsNavLink extends React.Component<Props> { } const { permissionUrl, t } = this.props; return ( - <NavLink to={permissionUrl} icon="fas fa-lock" label={t("repositoryRoot.menu.permissionsNavLink")} /> + <NavLink to={permissionUrl} label={t("repositoryRoot.menu.permissionsNavLink")} /> ); } } diff --git a/scm-ui/src/users/components/navLinks/GeneralUserNavLink.js b/scm-ui/src/users/components/navLinks/GeneralUserNavLink.js index a9b3b9a37f..e222a7bec2 100644 --- a/scm-ui/src/users/components/navLinks/GeneralUserNavLink.js +++ b/scm-ui/src/users/components/navLinks/GeneralUserNavLink.js @@ -21,7 +21,7 @@ class GeneralUserNavLink extends React.Component<Props> { if (!this.isEditable()) { return null; } - return <NavLink to={editUrl} icon="fas fa-cog" label={t("singleUser.menu.generalNavLink")} />; + return <NavLink to={editUrl} label={t("singleUser.menu.generalNavLink")} />; } } From df8f2af3416770c1db1e3a3374c17650ce03b134 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 23 Jan 2019 17:14:29 +0100 Subject: [PATCH 030/112] small icon correction --- .../packages/ui-components/src/navigation/NavLink.js | 2 +- .../packages/ui-components/src/navigation/SubNavigation.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/navigation/NavLink.js b/scm-ui-components/packages/ui-components/src/navigation/NavLink.js index 53b124ef31..98c3138a8f 100644 --- a/scm-ui-components/packages/ui-components/src/navigation/NavLink.js +++ b/scm-ui-components/packages/ui-components/src/navigation/NavLink.js @@ -28,7 +28,7 @@ class NavLink extends React.Component<Props> { let showIcon = null; if (icon) { - showIcon = (<><i className={icon}></i>{" "}</>); + showIcon = (<><i className={icon} />{" "}</>); } return ( diff --git a/scm-ui-components/packages/ui-components/src/navigation/SubNavigation.js b/scm-ui-components/packages/ui-components/src/navigation/SubNavigation.js index 7acfd93841..4d2a6b2a42 100644 --- a/scm-ui-components/packages/ui-components/src/navigation/SubNavigation.js +++ b/scm-ui-components/packages/ui-components/src/navigation/SubNavigation.js @@ -39,7 +39,8 @@ class SubNavigation extends React.Component<Props> { return ( <li> <Link className={this.isActive(route) ? "is-active" : ""} to={to}> - <><i className={defaultIcon}></i>{" "}</> + <i className={defaultIcon} /> + {" "} {label} </Link> {children} From 80e0ffdc61640e731029ecc1e046b133dd919b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= <maren.suewer@cloudogu.com> Date: Mon, 28 Jan 2019 15:12:26 +0100 Subject: [PATCH 031/112] disable add admin group/user button when entry is empty --- .../packages/ui-components/src/forms/AddEntryToTableField.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-ui-components/packages/ui-components/src/forms/AddEntryToTableField.js b/scm-ui-components/packages/ui-components/src/forms/AddEntryToTableField.js index e5c04eb613..013014cd98 100644 --- a/scm-ui-components/packages/ui-components/src/forms/AddEntryToTableField.js +++ b/scm-ui-components/packages/ui-components/src/forms/AddEntryToTableField.js @@ -48,7 +48,7 @@ class AddEntryToTableField extends React.Component<Props, State> { <AddButton label={buttonLabel} action={this.addButtonClicked} - disabled={disabled} + disabled={disabled || this.state.entryToAdd ===""} /> </div> ); From f06d2d67a3b65b6f6dd237bd57ffa5c19441982c Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Tue, 29 Jan 2019 08:22:43 +0100 Subject: [PATCH 032/112] renamed general to edit --- .../{GeneralGroupNavLink.js => EditGroupNavLink.js} | 4 ++-- ...neralGroupNavLink.test.js => EditGroupNavLink.test.js} | 6 +++--- scm-ui/src/groups/components/navLinks/index.js | 2 +- .../groups/containers/{GeneralGroup.js => EditGroup.js} | 4 ++-- scm-ui/src/groups/containers/SingleGroup.js | 8 ++++---- .../{GeneralRepoNavLink.js => EditRepoNavLink.js} | 4 ++-- ...GeneralRepoNavLink.test.js => EditRepoNavLink.test.js} | 6 +++--- .../src/repos/containers/{GeneralRepo.js => EditRepo.js} | 4 ++-- scm-ui/src/repos/containers/RepositoryRoot.js | 8 ++++---- .../{GeneralUserNavLink.js => EditUserNavLink.js} | 4 ++-- .../users/components/navLinks/GeneralUserNavLink.test.js | 6 +++--- scm-ui/src/users/components/navLinks/index.js | 2 +- .../src/users/containers/{GeneralUser.js => EditUser.js} | 4 ++-- scm-ui/src/users/containers/SingleUser.js | 8 ++++---- 14 files changed, 35 insertions(+), 35 deletions(-) rename scm-ui/src/groups/components/navLinks/{GeneralGroupNavLink.js => EditGroupNavLink.js} (82%) rename scm-ui/src/groups/components/navLinks/{GeneralGroupNavLink.test.js => EditGroupNavLink.test.js} (66%) rename scm-ui/src/groups/containers/{GeneralGroup.js => EditGroup.js} (97%) rename scm-ui/src/repos/components/{GeneralRepoNavLink.js => EditRepoNavLink.js} (83%) rename scm-ui/src/repos/components/{GeneralRepoNavLink.test.js => EditRepoNavLink.test.js} (81%) rename scm-ui/src/repos/containers/{GeneralRepo.js => EditRepo.js} (96%) rename scm-ui/src/users/components/navLinks/{GeneralUserNavLink.js => EditUserNavLink.js} (82%) rename scm-ui/src/users/containers/{GeneralUser.js => EditUser.js} (96%) diff --git a/scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.js b/scm-ui/src/groups/components/navLinks/EditGroupNavLink.js similarity index 82% rename from scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.js rename to scm-ui/src/groups/components/navLinks/EditGroupNavLink.js index b04e572937..9713c5c5c9 100644 --- a/scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.js +++ b/scm-ui/src/groups/components/navLinks/EditGroupNavLink.js @@ -10,7 +10,7 @@ type Props = { t: string => string }; -class GeneralGroupNavLink extends React.Component<Props> { +class EditGroupNavLink extends React.Component<Props> { isEditable = () => { return this.props.group._links.update; }; @@ -25,4 +25,4 @@ class GeneralGroupNavLink extends React.Component<Props> { } } -export default translate("groups")(GeneralGroupNavLink); +export default translate("groups")(EditGroupNavLink); diff --git a/scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.test.js b/scm-ui/src/groups/components/navLinks/EditGroupNavLink.test.js similarity index 66% rename from scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.test.js rename to scm-ui/src/groups/components/navLinks/EditGroupNavLink.test.js index 1120dab2fd..7399f4f714 100644 --- a/scm-ui/src/groups/components/navLinks/GeneralGroupNavLink.test.js +++ b/scm-ui/src/groups/components/navLinks/EditGroupNavLink.test.js @@ -4,14 +4,14 @@ import React from "react"; import { shallow } from "enzyme"; import "../../../tests/enzyme"; import "../../../tests/i18n"; -import GeneralGroupNavLink from "./GeneralGroupNavLink"; +import EditGroupNavLink from "./EditGroupNavLink"; it("should render nothing, if the edit link is missing", () => { const group = { _links: {} }; - const navLink = shallow(<GeneralGroupNavLink group={group} editUrl='/group/edit'/>); + const navLink = shallow(<EditGroupNavLink group={group} editUrl='/group/edit'/>); expect(navLink.text()).toBe(""); }); @@ -24,6 +24,6 @@ it("should render the navLink", () => { } }; - const navLink = shallow(<GeneralGroupNavLink group={group} editUrl='/group/edit'/>); + const navLink = shallow(<EditGroupNavLink group={group} editUrl='/group/edit'/>); expect(navLink.text()).not.toBe(""); }); diff --git a/scm-ui/src/groups/components/navLinks/index.js b/scm-ui/src/groups/components/navLinks/index.js index 88e2e7f4b5..992e9ab9a3 100644 --- a/scm-ui/src/groups/components/navLinks/index.js +++ b/scm-ui/src/groups/components/navLinks/index.js @@ -1,2 +1,2 @@ -export { default as GeneralGroupNavLink } from "./GeneralGroupNavLink"; +export { default as EditGroupNavLink } from "./EditGroupNavLink"; export { default as SetPermissionsNavLink } from "./SetPermissionsNavLink"; diff --git a/scm-ui/src/groups/containers/GeneralGroup.js b/scm-ui/src/groups/containers/EditGroup.js similarity index 97% rename from scm-ui/src/groups/containers/GeneralGroup.js rename to scm-ui/src/groups/containers/EditGroup.js index a89491568f..241a61356a 100644 --- a/scm-ui/src/groups/containers/GeneralGroup.js +++ b/scm-ui/src/groups/containers/EditGroup.js @@ -30,7 +30,7 @@ type Props = { error: Error }; -class GeneralGroup extends React.Component<Props> { +class EditGroup extends React.Component<Props> { componentDidMount() { const { group, modifyGroupReset } = this.props; modifyGroupReset(group); @@ -116,4 +116,4 @@ const mapDispatchToProps = dispatch => { export default connect( mapStateToProps, mapDispatchToProps -)(withRouter(GeneralGroup)); +)(withRouter(EditGroup)); diff --git a/scm-ui/src/groups/containers/SingleGroup.js b/scm-ui/src/groups/containers/SingleGroup.js index 3db1fa0006..f8d88d7a7d 100644 --- a/scm-ui/src/groups/containers/SingleGroup.js +++ b/scm-ui/src/groups/containers/SingleGroup.js @@ -13,7 +13,7 @@ import { import { Route } from "react-router"; import { Details } from "./../components/table"; import { - GeneralGroupNavLink, + EditGroupNavLink, SetPermissionsNavLink } from "./../components/navLinks"; import type { Group } from "@scm-manager/ui-types"; @@ -26,7 +26,7 @@ import { } from "../modules/groups"; import { translate } from "react-i18next"; -import GeneralGroup from "./GeneralGroup"; +import EditGroup from "./EditGroup"; import { getGroupsLink } from "../../modules/indexResource"; import SetPermissions from "../../permissions/components/SetPermissions"; import { ExtensionPoint } from "@scm-manager/ui-extensions"; @@ -99,7 +99,7 @@ class SingleGroup extends React.Component<Props> { <Route path={`${url}/settings/general`} exact - component={() => <GeneralGroup group={group} />} + component={() => <EditGroup group={group} />} /> <Route path={`${url}/settings/permissions`} @@ -133,7 +133,7 @@ class SingleGroup extends React.Component<Props> { to={`${url}/settings/general`} label={t("singleGroup.menu.settingsNavLink")} > - <GeneralGroupNavLink + <EditGroupNavLink group={group} editUrl={`${url}/settings/general`} /> diff --git a/scm-ui/src/repos/components/GeneralRepoNavLink.js b/scm-ui/src/repos/components/EditRepoNavLink.js similarity index 83% rename from scm-ui/src/repos/components/GeneralRepoNavLink.js rename to scm-ui/src/repos/components/EditRepoNavLink.js index 650cf0f62c..9a502cdafb 100644 --- a/scm-ui/src/repos/components/GeneralRepoNavLink.js +++ b/scm-ui/src/repos/components/EditRepoNavLink.js @@ -10,7 +10,7 @@ type Props = { t: string => string }; -class GeneralRepoNavLink extends React.Component<Props> { +class EditRepoNavLink extends React.Component<Props> { isEditable = () => { return this.props.repository._links.update; }; @@ -25,4 +25,4 @@ class GeneralRepoNavLink extends React.Component<Props> { } } -export default translate("repos")(GeneralRepoNavLink); +export default translate("repos")(EditRepoNavLink); diff --git a/scm-ui/src/repos/components/GeneralRepoNavLink.test.js b/scm-ui/src/repos/components/EditRepoNavLink.test.js similarity index 81% rename from scm-ui/src/repos/components/GeneralRepoNavLink.test.js rename to scm-ui/src/repos/components/EditRepoNavLink.test.js index 2ce050f69c..22bb06fae0 100644 --- a/scm-ui/src/repos/components/GeneralRepoNavLink.test.js +++ b/scm-ui/src/repos/components/EditRepoNavLink.test.js @@ -3,7 +3,7 @@ import { shallow, mount } from "enzyme"; import "../../tests/enzyme"; import "../../tests/i18n"; import ReactRouterEnzymeContext from "react-router-enzyme-context"; -import GeneralRepoNavLink from "./GeneralRepoNavLink"; +import EditRepoNavLink from "./EditRepoNavLink"; describe("GeneralNavLink", () => { const options = new ReactRouterEnzymeContext(); @@ -14,7 +14,7 @@ describe("GeneralNavLink", () => { }; const navLink = shallow( - <GeneralRepoNavLink repository={repository} editUrl="" />, + <EditRepoNavLink repository={repository} editUrl="" />, options.get() ); expect(navLink.text()).toBe(""); @@ -30,7 +30,7 @@ describe("GeneralNavLink", () => { }; const navLink = mount( - <GeneralRepoNavLink repository={repository} editUrl="" />, + <EditRepoNavLink repository={repository} editUrl="" />, options.get() ); expect(navLink.text()).toBe("repositoryRoot.menu.generalNavLink"); diff --git a/scm-ui/src/repos/containers/GeneralRepo.js b/scm-ui/src/repos/containers/EditRepo.js similarity index 96% rename from scm-ui/src/repos/containers/GeneralRepo.js rename to scm-ui/src/repos/containers/EditRepo.js index 83c5d47b52..072a45b670 100644 --- a/scm-ui/src/repos/containers/GeneralRepo.js +++ b/scm-ui/src/repos/containers/EditRepo.js @@ -28,7 +28,7 @@ type Props = { history: History }; -class GeneralRepo extends React.Component<Props> { +class EditRepo extends React.Component<Props> { componentDidMount() { const { modifyRepoReset, repository } = this.props; modifyRepoReset(repository); @@ -93,4 +93,4 @@ const mapDispatchToProps = dispatch => { export default connect( mapStateToProps, mapDispatchToProps -)(withRouter(GeneralRepo)); +)(withRouter(EditRepo)); diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 9291017ba9..583d351a97 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -22,11 +22,11 @@ import { } from "@scm-manager/ui-components"; import { translate } from "react-i18next"; import RepositoryDetails from "../components/RepositoryDetails"; -import GeneralRepo from "./GeneralRepo"; +import EditRepo from "./EditRepo"; import Permissions from "../permissions/containers/Permissions"; import type { History } from "history"; -import GeneralRepoNavLink from "../components/GeneralRepoNavLink"; +import EditRepoNavLink from "../components/EditRepoNavLink"; import BranchRoot from "./ChangesetsRoot"; import ChangesetView from "./ChangesetView"; @@ -113,7 +113,7 @@ class RepositoryRoot extends React.Component<Props> { /> <Route path={`${url}/settings/general`} - component={() => <GeneralRepo repository={repository} />} + component={() => <EditRepo repository={repository} />} /> <Route path={`${url}/settings/permissions`} @@ -203,7 +203,7 @@ class RepositoryRoot extends React.Component<Props> { to={`${url}/settings/general`} label={t("repositoryRoot.menu.settingsNavLink")} > - <GeneralRepoNavLink + <EditRepoNavLink repository={repository} editUrl={`${url}/settings/general`} /> diff --git a/scm-ui/src/users/components/navLinks/GeneralUserNavLink.js b/scm-ui/src/users/components/navLinks/EditUserNavLink.js similarity index 82% rename from scm-ui/src/users/components/navLinks/GeneralUserNavLink.js rename to scm-ui/src/users/components/navLinks/EditUserNavLink.js index e222a7bec2..051bf9a4bd 100644 --- a/scm-ui/src/users/components/navLinks/GeneralUserNavLink.js +++ b/scm-ui/src/users/components/navLinks/EditUserNavLink.js @@ -10,7 +10,7 @@ type Props = { t: string => string }; -class GeneralUserNavLink extends React.Component<Props> { +class EditUserNavLink extends React.Component<Props> { isEditable = () => { return this.props.user._links.update; }; @@ -25,4 +25,4 @@ class GeneralUserNavLink extends React.Component<Props> { } } -export default translate("users")(GeneralUserNavLink); +export default translate("users")(EditUserNavLink); diff --git a/scm-ui/src/users/components/navLinks/GeneralUserNavLink.test.js b/scm-ui/src/users/components/navLinks/GeneralUserNavLink.test.js index 018e98f47e..ec46c531a1 100644 --- a/scm-ui/src/users/components/navLinks/GeneralUserNavLink.test.js +++ b/scm-ui/src/users/components/navLinks/GeneralUserNavLink.test.js @@ -2,14 +2,14 @@ import React from "react"; import { shallow } from "enzyme"; import "../../../tests/enzyme"; import "../../../tests/i18n"; -import GeneralUserNavLink from "./GeneralUserNavLink"; +import EditUserNavLink from "./EditUserNavLink"; it("should render nothing, if the edit link is missing", () => { const user = { _links: {} }; - const navLink = shallow(<GeneralUserNavLink user={user} editUrl='/user/edit'/>); + const navLink = shallow(<EditUserNavLink user={user} editUrl='/user/edit'/>); expect(navLink.text()).toBe(""); }); @@ -22,6 +22,6 @@ it("should render the navLink", () => { } }; - const navLink = shallow(<GeneralUserNavLink user={user} editUrl='/user/edit'/>); + const navLink = shallow(<EditUserNavLink user={user} editUrl='/user/edit'/>); expect(navLink.text()).not.toBe(""); }); diff --git a/scm-ui/src/users/components/navLinks/index.js b/scm-ui/src/users/components/navLinks/index.js index 87eda98f68..cb97c57e3f 100644 --- a/scm-ui/src/users/components/navLinks/index.js +++ b/scm-ui/src/users/components/navLinks/index.js @@ -1,3 +1,3 @@ -export { default as GeneralUserNavLink } from "./GeneralUserNavLink"; +export { default as EditUserNavLink } from "./EditUserNavLink"; export { default as SetPasswordNavLink } from "./SetPasswordNavLink"; export { default as SetPermissionsNavLink } from "./SetPermissionsNavLink"; diff --git a/scm-ui/src/users/containers/GeneralUser.js b/scm-ui/src/users/containers/EditUser.js similarity index 96% rename from scm-ui/src/users/containers/GeneralUser.js rename to scm-ui/src/users/containers/EditUser.js index cb578d6f1e..6b061c48bf 100644 --- a/scm-ui/src/users/containers/GeneralUser.js +++ b/scm-ui/src/users/containers/EditUser.js @@ -31,7 +31,7 @@ type Props = { history: History }; -class GeneralUser extends React.Component<Props> { +class EditUser extends React.Component<Props> { componentDidMount() { const { modifyUserReset, user } = this.props; modifyUserReset(user); @@ -96,4 +96,4 @@ const mapDispatchToProps = dispatch => { export default connect( mapStateToProps, mapDispatchToProps -)(withRouter(GeneralUser)); +)(withRouter(EditUser)); diff --git a/scm-ui/src/users/containers/SingleUser.js b/scm-ui/src/users/containers/SingleUser.js index 64ed7946e4..9a1633a162 100644 --- a/scm-ui/src/users/containers/SingleUser.js +++ b/scm-ui/src/users/containers/SingleUser.js @@ -12,7 +12,7 @@ import { } from "@scm-manager/ui-components"; import { Route } from "react-router"; import { Details } from "./../components/table"; -import GeneralUser from "./GeneralUser"; +import EditUser from "./EditUser"; import type { User } from "@scm-manager/ui-types"; import type { History } from "history"; import { @@ -21,7 +21,7 @@ import { isFetchUserPending, getFetchUserFailure } from "../modules/users"; -import { GeneralUserNavLink, SetPasswordNavLink, SetPermissionsNavLink } from "./../components/navLinks"; +import { EditUserNavLink, SetPasswordNavLink, SetPermissionsNavLink } from "./../components/navLinks"; import { translate } from "react-i18next"; import { getUsersLink } from "../../modules/indexResource"; import SetUserPassword from "../components/SetUserPassword"; @@ -91,7 +91,7 @@ class SingleUser extends React.Component<Props> { <Route path={url} exact component={() => <Details user={user} />} /> <Route path={`${url}/settings/general`} - component={() => <GeneralUser user={user} />} + component={() => <EditUser user={user} />} /> <Route path={`${url}/settings/password`} @@ -118,7 +118,7 @@ class SingleUser extends React.Component<Props> { to={`${url}/settings/general`} label={t("singleUser.menu.settingsNavLink")} > - <GeneralUserNavLink + <EditUserNavLink user={user} editUrl={`${url}/settings/general`} /> From a13a144c21c0712b711dfd60560bfef6e0608bae Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Tue, 29 Jan 2019 08:58:45 +0100 Subject: [PATCH 033/112] redesigned submenu --- .../src/navigation/SubNavigation.js | 20 ++++---- scm-ui/styles/scm.scss | 51 ++++++++++++++----- 2 files changed, 48 insertions(+), 23 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/navigation/SubNavigation.js b/scm-ui-components/packages/ui-components/src/navigation/SubNavigation.js index 4d2a6b2a42..0a6612a173 100644 --- a/scm-ui-components/packages/ui-components/src/navigation/SubNavigation.js +++ b/scm-ui-components/packages/ui-components/src/navigation/SubNavigation.js @@ -1,6 +1,6 @@ //@flow import * as React from "react"; -import {Link, Route} from "react-router-dom"; +import { Link, Route } from "react-router-dom"; type Props = { to: string, @@ -30,18 +30,14 @@ class SubNavigation extends React.Component<Props> { } let children = null; - if(this.isActive(route)) { - children = ( - <ul>{this.props.children}</ul> - ); + if (this.isActive(route)) { + children = <ul className="sub-menu">{this.props.children}</ul>; } return ( <li> <Link className={this.isActive(route) ? "is-active" : ""} to={to}> - <i className={defaultIcon} /> - {" "} - {label} + <i className={defaultIcon} /> {label} </Link> {children} </li> @@ -53,11 +49,15 @@ class SubNavigation extends React.Component<Props> { // removes last part of url let parents = to.split("/"); - parents.splice(-1,1); + parents.splice(-1, 1); let parent = parents.join("/"); return ( - <Route path={parent} exact={activeOnlyWhenExact} children={this.renderLink} /> + <Route + path={parent} + exact={activeOnlyWhenExact} + children={this.renderLink} + /> ); } } diff --git a/scm-ui/styles/scm.scss b/scm-ui/styles/scm.scss index 3ceab48f9f..a5d2eda70e 100644 --- a/scm-ui/styles/scm.scss +++ b/scm-ui/styles/scm.scss @@ -259,21 +259,10 @@ $fa-font-path: "webfonts"; &.is-active { color: $blue; background-color: #fff; - - &:before { - position: relative; - content: " "; - background: $blue; - height: 53px; - width: 2px; - display: block; - left: -17px; - float: left; - top: -16px; - } } } - li { + + > li { ul { margin: 0; border-top: 1px solid #eee; @@ -286,6 +275,18 @@ $fa-font-path: "webfonts"; } } + > a.is-active:before { + position: relative; + content: " "; + background: $blue; + height: 53px; + width: 2px; + display: block; + left: -17px; + float: left; + top: -16px; + } + border-radius: 0; border-top: 1px solid #eee; border-left: 1px solid #eee; @@ -301,3 +302,27 @@ $fa-font-path: "webfonts"; margin-bottom: 0; } } +.sub-menu li { + line-height: 1; + + a { + padding: 0.75rem 1rem; + } + + a:before { + font-family: "Font Awesome 5 Free"; + font-weight: 900; + -webkit-font-smoothing: antialiased; + display: inline-block; + font-style: normal; + font-variant: normal; + text-rendering: auto; + line-height: 1; + content: "\f105"; + padding-right: 5px; + } + + i { + display: none; + } +} From d201c156f0fb927dd3efbf21448ec9d55214538a Mon Sep 17 00:00:00 2001 From: Mohamed Karray <mohamed.karray.sr@gmail.com> Date: Tue, 29 Jan 2019 14:05:12 +0100 Subject: [PATCH 034/112] fix get modifications for SVN --- .../spi/SvnModificationsCommand.java | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModificationsCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModificationsCommand.java index 4b4f655b12..bbcaa9a9d5 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModificationsCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModificationsCommand.java @@ -4,6 +4,8 @@ import lombok.extern.slf4j.Slf4j; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNLogEntry; import org.tmatesoft.svn.core.io.SVNRepository; +import org.tmatesoft.svn.core.wc.SVNClientManager; +import org.tmatesoft.svn.core.wc.admin.SVNLookClient; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Modifications; import sonia.scm.repository.Repository; @@ -23,20 +25,31 @@ public class SvnModificationsCommand extends AbstractSvnCommand implements Modif @Override @SuppressWarnings("unchecked") public Modifications getModifications(String revision) { - Modifications modifications = null; + final Modifications modifications = new Modifications(); log.debug("get modifications {}", revision); try { - long revisionNumber = SvnUtil.parseRevision(revision, repository); - SVNRepository repo = open(); - Collection<SVNLogEntry> entries = repo.log(null, null, revisionNumber, - revisionNumber, true, true); - if (Util.isNotEmpty(entries)) { - modifications = SvnUtil.createModifications(entries.iterator().next(), revision); + if (SvnUtil.isTransactionEntryId(revision)) { + + SVNLookClient client = SVNClientManager.newInstance().getLookClient(); + client.doGetChanged(context.getDirectory(), SvnUtil.getTransactionId(revision), + e -> SvnUtil.appendModification(modifications, e.getType(), e.getPath()), true); + + return modifications; + + } else { + + long revisionNumber = SvnUtil.getRevisionNumber(revision, repository); + SVNRepository repo = open(); + Collection<SVNLogEntry> entries = repo.log(null, null, revisionNumber, + revisionNumber, true, true); + if (Util.isNotEmpty(entries)) { + return SvnUtil.createModifications(entries.iterator().next(), revision); + } } } catch (SVNException ex) { throw new InternalRepositoryException(repository, "could not open repository", ex); } - return modifications; + return null; } @Override From 9959eb375b99fbfce9fc4ee5efc970a239b28003 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Tue, 29 Jan 2019 15:17:51 +0100 Subject: [PATCH 035/112] added TableHeader component --- .../ui-components/src/BranchSelector.js | 16 ++------- .../packages/ui-components/src/TableHeader.js | 36 +++++++++++++++++++ .../packages/ui-components/src/index.js | 1 + 3 files changed, 40 insertions(+), 13 deletions(-) create mode 100644 scm-ui-components/packages/ui-components/src/TableHeader.js diff --git a/scm-ui-components/packages/ui-components/src/BranchSelector.js b/scm-ui-components/packages/ui-components/src/BranchSelector.js index d03011bfdd..f44de639ba 100644 --- a/scm-ui-components/packages/ui-components/src/BranchSelector.js +++ b/scm-ui-components/packages/ui-components/src/BranchSelector.js @@ -2,6 +2,7 @@ import React from "react"; import type {Branch} from "@scm-manager/ui-types"; +import TableHeader from "./TableHeader"; import injectSheet from "react-jss"; import classNames from "classnames"; import DropDown from "./forms/DropDown"; @@ -12,11 +13,6 @@ const styles = { }, minWidthOfLabel: { minWidth: "4.5rem" - }, - wrapper: { - padding: "1rem 1.5rem 0.25rem 1.5rem", - border: "1px solid #eee", - borderRadius: "5px 5px 0 0" } }; @@ -48,13 +44,7 @@ class BranchSelector extends React.Component<Props, State> { if (branches) { return ( - <div - className={classNames( - "has-background-light field", - "is-horizontal", - classes.wrapper - )} - > + <TableHeader> <div className={classNames( "field-label", @@ -81,7 +71,7 @@ class BranchSelector extends React.Component<Props, State> { </div> </div> </div> - </div> + </TableHeader> ); } else { return null; diff --git a/scm-ui-components/packages/ui-components/src/TableHeader.js b/scm-ui-components/packages/ui-components/src/TableHeader.js new file mode 100644 index 0000000000..8b38160027 --- /dev/null +++ b/scm-ui-components/packages/ui-components/src/TableHeader.js @@ -0,0 +1,36 @@ +//@flow +import * as React from "react"; +import classNames from "classnames"; +import injectSheet from "react-jss"; + +type Props = { + children?: React.Node, + classes: Object +}; + +const styles = { + wrapper: { + padding: "1rem 1.5rem 0.25rem 1.5rem", + border: "1px solid #eee", + borderRadius: "5px 5px 0 0" + } +}; + +class TableHeader extends React.Component<Props> { + render() { + const { classes, children } = this.props; + return ( + <div + className={classNames( + "has-background-light field", + "is-horizontal", + classes.wrapper + )} + > + {children} + </div> + ); + } +} + +export default injectSheet(styles)(TableHeader); diff --git a/scm-ui-components/packages/ui-components/src/index.js b/scm-ui-components/packages/ui-components/src/index.js index 5b3cdb4c95..d510e28ade 100644 --- a/scm-ui-components/packages/ui-components/src/index.js +++ b/scm-ui-components/packages/ui-components/src/index.js @@ -25,6 +25,7 @@ export { default as Tooltip } from "./Tooltip"; export { getPageFromMatch } from "./urls"; export { default as Autocomplete} from "./Autocomplete"; export { default as BranchSelector } from "./BranchSelector"; +export { default as TableHeader } from "./TableHeader"; export { apiClient, NOT_FOUND_ERROR, UNAUTHORIZED_ERROR, CONFLICT_ERROR } from "./apiclient.js"; From d70c2bd6245ff169691115d7ca4af6532d2e2fae Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Tue, 29 Jan 2019 16:38:01 +0100 Subject: [PATCH 036/112] changed border handling --- .../packages/ui-components/src/TableHeader.js | 3 +-- scm-ui/src/repos/containers/ChangesetsRoot.js | 4 ++-- scm-ui/src/repos/sources/containers/Sources.js | 2 +- scm-ui/styles/scm.scss | 9 +++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/TableHeader.js b/scm-ui-components/packages/ui-components/src/TableHeader.js index 8b38160027..f7463d3136 100644 --- a/scm-ui-components/packages/ui-components/src/TableHeader.js +++ b/scm-ui-components/packages/ui-components/src/TableHeader.js @@ -11,8 +11,7 @@ type Props = { const styles = { wrapper: { padding: "1rem 1.5rem 0.25rem 1.5rem", - border: "1px solid #eee", - borderRadius: "5px 5px 0 0" + borderBottom: "1px solid #dbdbdb" } }; diff --git a/scm-ui/src/repos/containers/ChangesetsRoot.js b/scm-ui/src/repos/containers/ChangesetsRoot.js index 2eea64b8e1..c65c8f32cb 100644 --- a/scm-ui/src/repos/containers/ChangesetsRoot.js +++ b/scm-ui/src/repos/containers/ChangesetsRoot.js @@ -89,10 +89,10 @@ class BranchRoot extends React.Component<Props> { const changesets = <Changesets repository={repository} branch={branch} />; return ( - <> + <div className="has-border-around is-round"> {this.renderBranchSelector()} <Route path={`${url}/:page?`} component={() => changesets} /> - </> + </div> ); } diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index 810e2309ee..321c1faa16 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -93,7 +93,7 @@ class Sources extends React.Component<Props> { if (currentFileIsDirectory) { return ( - <div className={"has-border-around"}> + <div className="has-border-around is-round"> {this.renderBranchSelector()} <FileTree repository={repository} diff --git a/scm-ui/styles/scm.scss b/scm-ui/styles/scm.scss index d31f40ceea..620410bd48 100644 --- a/scm-ui/styles/scm.scss +++ b/scm-ui/styles/scm.scss @@ -87,10 +87,11 @@ $fa-font-path: "webfonts"; //border around options .has-border-around { - border-top: 1px solid #eee; - border-left: 1px solid #eee; - border-right: 1px solid #eee; - border-bottom: 1px solid #eee; + border: 1px solid #dbdbdb; + + &.is-round { + border-radius: 4px; + } } // multiline Columns From 258e11025a61894606b69b336fd2f3b8dd06e769 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Tue, 29 Jan 2019 17:12:18 +0100 Subject: [PATCH 037/112] changed panel usage + added pannel-footer --- .../src/repos/changesets/ChangesetList.js | 21 +++++++++++++++---- scm-ui/src/repos/containers/Changesets.js | 2 +- .../src/repos/sources/containers/Content.js | 6 ++---- .../repos/sources/containers/HistoryView.js | 12 ++++++----- .../repos/sources/containers/SourcesView.js | 17 +++++++++++---- scm-ui/styles/scm.scss | 18 ++++++++++++++++ 6 files changed, 58 insertions(+), 18 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetList.js b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetList.js index 74ec816369..c50c8a2d50 100644 --- a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetList.js +++ b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetList.js @@ -1,17 +1,26 @@ // @flow import ChangesetRow from "./ChangesetRow"; import React from "react"; +import injectSheet from "react-jss"; +import classNames from "classnames"; import type { Changeset, Repository } from "@scm-manager/ui-types"; type Props = { repository: Repository, - changesets: Changeset[] + changesets: Changeset[], + classes: any +}; + +const styles = { + toCenterContent: { + display: "block" + } }; class ChangesetList extends React.Component<Props> { render() { - const { repository, changesets } = this.props; + const { repository, changesets, classes } = this.props; const content = changesets.map(changeset => { return ( <ChangesetRow @@ -21,8 +30,12 @@ class ChangesetList extends React.Component<Props> { /> ); }); - return <div className="box">{content}</div>; + return ( + <div className={classNames("panel-block", classes.toCenterContent)}> + {content} + </div> + ); } } -export default ChangesetList; +export default injectSheet(styles)(ChangesetList); diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index 95bf0459a6..69dc41da2d 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -70,7 +70,7 @@ class Changesets extends React.Component<Props> { renderPaginator = () => { const { page, list } = this.props; if (list) { - return <LinkPaginator page={page} collection={list} />; + return <div className="panel-footer"><LinkPaginator page={page} collection={list} /></div>; } return null; }; diff --git a/scm-ui/src/repos/sources/containers/Content.js b/scm-ui/src/repos/sources/containers/Content.js index 6339c49a3d..cc4aa32b5b 100644 --- a/scm-ui/src/repos/sources/containers/Content.js +++ b/scm-ui/src/repos/sources/containers/Content.js @@ -161,7 +161,7 @@ class Content extends React.Component<Props, State> { } render() { - const { file, revision, repository, path, classes } = this.props; + const { file, revision, repository, path } = this.props; const { showHistory } = this.state; const header = this.showHeader(); @@ -183,9 +183,7 @@ class Content extends React.Component<Props, State> { <nav className="panel"> <article className="panel-heading">{header}</article> {moreInformation} - <div className={classNames("panel-block", classes.toCenterContent)}> - {content} - </div> + {content} </nav> </div> ); diff --git a/scm-ui/src/repos/sources/containers/HistoryView.js b/scm-ui/src/repos/sources/containers/HistoryView.js index 98400248d9..c118ef9cea 100644 --- a/scm-ui/src/repos/sources/containers/HistoryView.js +++ b/scm-ui/src/repos/sources/containers/HistoryView.js @@ -80,11 +80,13 @@ class HistoryView extends React.Component<Props, State> { return ( <> <ChangesetList repository={repository} changesets={changesets} /> - <StatePaginator - page={currentPage} - collection={pageCollection} - updatePage={(newPage: number) => this.updatePage(newPage)} - /> + <div className="panel-footer"> + <StatePaginator + page={currentPage} + collection={pageCollection} + updatePage={(newPage: number) => this.updatePage(newPage)} + /> + </div> </> ); } diff --git a/scm-ui/src/repos/sources/containers/SourcesView.js b/scm-ui/src/repos/sources/containers/SourcesView.js index 1e76c6d6bf..220c6ecc27 100644 --- a/scm-ui/src/repos/sources/containers/SourcesView.js +++ b/scm-ui/src/repos/sources/containers/SourcesView.js @@ -8,12 +8,15 @@ import { ExtensionPoint } from "@scm-manager/ui-extensions"; import { getContentType } from "./contentType"; import type { File, Repository } from "@scm-manager/ui-types"; import { ErrorNotification, Loading } from "@scm-manager/ui-components"; +import injectSheet from "react-jss"; +import classNames from "classnames"; type Props = { repository: Repository, file: File, revision: string, - path: string + path: string, + classes: any }; type State = { @@ -23,6 +26,12 @@ type State = { error?: Error }; +const styles = { + toCenterContent: { + display: "block" + } +}; + class SourcesView extends React.Component<Props, State> { constructor(props: Props) { super(props); @@ -78,7 +87,7 @@ class SourcesView extends React.Component<Props, State> { } render() { - const { file } = this.props; + const { file, classes } = this.props; const { loaded, error } = this.state; if (!file || !loaded) { @@ -90,8 +99,8 @@ class SourcesView extends React.Component<Props, State> { const sources = this.showSources(); - return <>{sources}</>; + return <div className={classNames("panel-block", classes.toCenterContent)}>{sources}</div>; } } -export default SourcesView; +export default injectSheet(styles)(SourcesView); diff --git a/scm-ui/styles/scm.scss b/scm-ui/styles/scm.scss index 620410bd48..59f1ca07a4 100644 --- a/scm-ui/styles/scm.scss +++ b/scm-ui/styles/scm.scss @@ -194,6 +194,24 @@ $fa-font-path: "webfonts"; } } +//panels +.panel-footer { + background-color: whitesmoke; + border-radius: 0 0 4px 4px; + color: #363636; + font-size: 1.25em; + font-weight: 300; + line-height: 1.25; + padding: 0.5em 0.75em; + + border-left: 1px solid #dbdbdb; + border-right: 1px solid #dbdbdb; + + &:last-child { + border-bottom: 1px solid #dbdbdb; + } +} + // forms .field:not(.is-grouped) { margin-bottom: 1rem; From 6edc3d12c7c89a7b99c7169ecfd75183769288f3 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Tue, 29 Jan 2019 17:45:47 +0100 Subject: [PATCH 038/112] =?UTF-8?q?replaced=20self-hacked=E2=84=A2=20solut?= =?UTF-8?q?ion=20with=20bulma=20panels=20for=20changesets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui-components/src/BranchSelector.js | 31 ++++++++++++---- .../packages/ui-components/src/TableHeader.js | 35 ------------------- .../packages/ui-components/src/index.js | 1 - scm-ui/src/repos/containers/ChangesetsRoot.js | 6 ++-- 4 files changed, 28 insertions(+), 45 deletions(-) delete mode 100644 scm-ui-components/packages/ui-components/src/TableHeader.js diff --git a/scm-ui-components/packages/ui-components/src/BranchSelector.js b/scm-ui-components/packages/ui-components/src/BranchSelector.js index f44de639ba..c473527472 100644 --- a/scm-ui-components/packages/ui-components/src/BranchSelector.js +++ b/scm-ui-components/packages/ui-components/src/BranchSelector.js @@ -1,8 +1,7 @@ // @flow import React from "react"; -import type {Branch} from "@scm-manager/ui-types"; -import TableHeader from "./TableHeader"; +import type { Branch } from "@scm-manager/ui-types"; import injectSheet from "react-jss"; import classNames from "classnames"; import DropDown from "./forms/DropDown"; @@ -13,6 +12,12 @@ const styles = { }, minWidthOfLabel: { minWidth: "4.5rem" + }, + labelSizing: { + fontSize: "1rem !important" + }, + noBottomMargin: { + marginBottom: "0 !important" } }; @@ -35,7 +40,9 @@ class BranchSelector extends React.Component<Props, State> { } componentDidMount() { - const selectedBranch = this.props.branches.find(branch => branch.name === this.props.selectedBranch); + const selectedBranch = this.props.branches.find( + branch => branch.name === this.props.selectedBranch + ); this.setState({ selectedBranch }); } @@ -44,7 +51,13 @@ class BranchSelector extends React.Component<Props, State> { if (branches) { return ( - <TableHeader> + <div + className={classNames( + "field", + "is-horizontal", + classes.noBottomMargin + )} + > <div className={classNames( "field-label", @@ -53,10 +66,14 @@ class BranchSelector extends React.Component<Props, State> { classes.minWidthOfLabel )} > - <label className="label">{label}</label> + <label className={classNames("label", classes.labelSizing)}> + {label} + </label> </div> <div className="field-body"> - <div className="field is-narrow"> + <div + className={classNames("field is-narrow", classes.noBottomMargin)} + > <div className="control"> <DropDown className="is-fullwidth" @@ -71,7 +88,7 @@ class BranchSelector extends React.Component<Props, State> { </div> </div> </div> - </TableHeader> + </div> ); } else { return null; diff --git a/scm-ui-components/packages/ui-components/src/TableHeader.js b/scm-ui-components/packages/ui-components/src/TableHeader.js deleted file mode 100644 index f7463d3136..0000000000 --- a/scm-ui-components/packages/ui-components/src/TableHeader.js +++ /dev/null @@ -1,35 +0,0 @@ -//@flow -import * as React from "react"; -import classNames from "classnames"; -import injectSheet from "react-jss"; - -type Props = { - children?: React.Node, - classes: Object -}; - -const styles = { - wrapper: { - padding: "1rem 1.5rem 0.25rem 1.5rem", - borderBottom: "1px solid #dbdbdb" - } -}; - -class TableHeader extends React.Component<Props> { - render() { - const { classes, children } = this.props; - return ( - <div - className={classNames( - "has-background-light field", - "is-horizontal", - classes.wrapper - )} - > - {children} - </div> - ); - } -} - -export default injectSheet(styles)(TableHeader); diff --git a/scm-ui-components/packages/ui-components/src/index.js b/scm-ui-components/packages/ui-components/src/index.js index d510e28ade..5b3cdb4c95 100644 --- a/scm-ui-components/packages/ui-components/src/index.js +++ b/scm-ui-components/packages/ui-components/src/index.js @@ -25,7 +25,6 @@ export { default as Tooltip } from "./Tooltip"; export { getPageFromMatch } from "./urls"; export { default as Autocomplete} from "./Autocomplete"; export { default as BranchSelector } from "./BranchSelector"; -export { default as TableHeader } from "./TableHeader"; export { apiClient, NOT_FOUND_ERROR, UNAUTHORIZED_ERROR, CONFLICT_ERROR } from "./apiclient.js"; diff --git a/scm-ui/src/repos/containers/ChangesetsRoot.js b/scm-ui/src/repos/containers/ChangesetsRoot.js index c65c8f32cb..d3d4635e18 100644 --- a/scm-ui/src/repos/containers/ChangesetsRoot.js +++ b/scm-ui/src/repos/containers/ChangesetsRoot.js @@ -89,10 +89,12 @@ class BranchRoot extends React.Component<Props> { const changesets = <Changesets repository={repository} branch={branch} />; return ( - <div className="has-border-around is-round"> + <nav className="panel"> + <article className="panel-heading"> {this.renderBranchSelector()} + </article> <Route path={`${url}/:page?`} component={() => changesets} /> - </div> + </nav> ); } From e99752ed2e152f5adec7c7a37628d7404e4a8050 Mon Sep 17 00:00:00 2001 From: Mohamed Karray <mohamed.karray.sr@gmail.com> Date: Tue, 29 Jan 2019 17:46:36 +0100 Subject: [PATCH 039/112] move the ChangesetDto to core because the activity plugin need it. --- .../src/main/java/sonia/scm/api/v2/resources/ChangesetDto.java | 0 .../src/main/java/sonia/scm/api/v2/resources/PersonDto.java | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {scm-webapp => scm-core}/src/main/java/sonia/scm/api/v2/resources/ChangesetDto.java (100%) rename {scm-webapp => scm-core}/src/main/java/sonia/scm/api/v2/resources/PersonDto.java (100%) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetDto.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/ChangesetDto.java similarity index 100% rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetDto.java rename to scm-core/src/main/java/sonia/scm/api/v2/resources/ChangesetDto.java diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PersonDto.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/PersonDto.java similarity index 100% rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/PersonDto.java rename to scm-core/src/main/java/sonia/scm/api/v2/resources/PersonDto.java From dfd187a2474c0f13be8353f8bafbb6e91c19b861 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Wed, 30 Jan 2019 11:25:01 +0100 Subject: [PATCH 040/112] integrate sonatype lifecycle analysis into jenkins build --- Jenkinsfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index 8bb52d6030..e817ac2ba1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -50,6 +50,11 @@ node('docker') { def dockerImageTag = "2.0.0-dev-${commitHash.substring(0,7)}-${BUILD_NUMBER}" if (isMainBranch()) { + + stage('Lifecycle') { + nexusPolicyEvaluation iqApplication: selectedApplication('scm'), iqScanPatterns: [[scanPattern: 'scm-server/target/scm-server-app.zip']], iqStage: 'build' + } + stage('Archive') { archiveArtifacts 'scm-webapp/target/scm-webapp.war' archiveArtifacts 'scm-server/target/scm-server-app.*' From 69dda6403d3d25ed90e8467990b81599ab03bcee Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Wed, 30 Jan 2019 13:21:11 +0100 Subject: [PATCH 041/112] update resteasy to v3.6.2.Final in order to fix CVE-2017-7561 and CVE-2016-6347 --- pom.xml | 4 +-- scm-core/pom.xml | 2 +- .../v2/resources/GitConfigResourceTest.java | 15 +++++----- .../v2/resources/HgConfigResourceTest.java | 5 ++-- .../v2/resources/SvnConfigResourceTest.java | 6 ++-- scm-webapp/pom.xml | 14 ++++----- .../api/v2/resources/ConfigResourceTest.java | 3 +- .../v2/resources/GroupRootResourceTest.java | 9 +++--- .../scm/api/v2/resources/MeResourceTest.java | 3 +- .../RepositoryPermissionRootResourceTest.java | 29 ++++++++++++++----- .../resources/RepositoryRootResourceTest.java | 7 +++-- .../api/v2/resources/UIRootResourceTest.java | 7 +++-- .../v2/resources/UserRootResourceTest.java | 13 +++++---- 13 files changed, 70 insertions(+), 47 deletions(-) diff --git a/pom.xml b/pom.xml index 2d8bc13f28..cae4aa9bb6 100644 --- a/pom.xml +++ b/pom.xml @@ -825,8 +825,8 @@ <logback.version>1.2.3</logback.version> <servlet.version>3.0.1</servlet.version> - <jaxrs.version>2.0.1</jaxrs.version> - <resteasy.version>3.1.3.Final</resteasy.version> + <jaxrs.version>2.1.1</jaxrs.version> + <resteasy.version>3.6.2.Final</resteasy.version> <jersey-client.version>1.19.4</jersey-client.version> <enunciate.version>2.11.1</enunciate.version> <jackson.version>2.8.6</jackson.version> diff --git a/scm-core/pom.xml b/scm-core/pom.xml index c4bf3a2e6f..f19d50064d 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -93,6 +93,7 @@ <dependency> <groupId>javax.ws.rs</groupId> <artifactId>javax.ws.rs-api</artifactId> + <scope>provided</scope> </dependency> <dependency> @@ -235,7 +236,6 @@ <links> <link>http://download.oracle.com/javase/6/docs/api/</link> <link>http://download.oracle.com/docs/cd/E17802_01/products/products/servlet/2.5/docs/servlet-2_5-mr2/</link> - <link>http://jersey.java.net/nonav/apidocs/${jersey.version}/jersey/</link> <link>https://google.github.io/guice/api-docs/${guice.version}/javadoc</link> <link>http://www.slf4j.org/api/</link> <link>http://shiro.apache.org/static/${shiro.version}/apidocs/</link> diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java index 0c28a28e59..8e657b1050 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java @@ -17,7 +17,7 @@ import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.GitConfig; import sonia.scm.repository.GitRepositoryConfig; import sonia.scm.repository.GitRepositoryHandler; @@ -29,6 +29,7 @@ import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.web.GitVndMediaType; import javax.servlet.http.HttpServletResponse; +import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; @@ -100,7 +101,7 @@ public class GitConfigResourceTest { @Test @SubjectAware(username = "readWrite") - public void shouldGetGitConfig() throws URISyntaxException { + public void shouldGetGitConfig() throws URISyntaxException, UnsupportedEncodingException { MockHttpResponse response = get(); assertEquals(HttpServletResponse.SC_OK, response.getStatus()); @@ -115,7 +116,7 @@ public class GitConfigResourceTest { @Test @SubjectAware(username = "readWrite") - public void shouldGetGitConfigEvenWhenItsEmpty() throws URISyntaxException { + public void shouldGetGitConfigEvenWhenItsEmpty() throws URISyntaxException, UnsupportedEncodingException { when(repositoryHandler.getConfig()).thenReturn(null); MockHttpResponse response = get(); @@ -126,7 +127,7 @@ public class GitConfigResourceTest { @Test @SubjectAware(username = "readOnly") - public void shouldGetGitConfigWithoutUpdateLink() throws URISyntaxException { + public void shouldGetGitConfigWithoutUpdateLink() throws URISyntaxException, UnsupportedEncodingException { MockHttpResponse response = get(); assertEquals(HttpServletResponse.SC_OK, response.getStatus()); @@ -159,7 +160,7 @@ public class GitConfigResourceTest { @Test @SubjectAware(username = "writeOnly") - public void shouldReadDefaultRepositoryConfig() throws URISyntaxException { + public void shouldReadDefaultRepositoryConfig() throws URISyntaxException, UnsupportedEncodingException { when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(new Repository("id", "git", "space", "X")); MockHttpRequest request = MockHttpRequest.get("/" + GitConfigResource.GIT_CONFIG_PATH_V2 + "/space/X"); @@ -176,7 +177,7 @@ public class GitConfigResourceTest { @Test @SubjectAware(username = "readOnly") - public void shouldNotHaveUpdateLinkForReadOnlyUser() throws URISyntaxException { + public void shouldNotHaveUpdateLinkForReadOnlyUser() throws URISyntaxException, UnsupportedEncodingException { when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(new Repository("id", "git", "space", "X")); MockHttpRequest request = MockHttpRequest.get("/" + GitConfigResource.GIT_CONFIG_PATH_V2 + "/space/X"); @@ -193,7 +194,7 @@ public class GitConfigResourceTest { @Test @SubjectAware(username = "writeOnly") - public void shouldReadStoredRepositoryConfig() throws URISyntaxException { + public void shouldReadStoredRepositoryConfig() throws URISyntaxException, UnsupportedEncodingException { when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(new Repository("id", "git", "space", "X")); GitRepositoryConfig gitRepositoryConfig = new GitRepositoryConfig(); gitRepositoryConfig.setDefaultBranch("test"); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java index df59954971..e9b96fd71b 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java @@ -25,6 +25,7 @@ import javax.inject.Provider; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; @@ -99,7 +100,7 @@ public class HgConfigResourceTest { @Test @SubjectAware(username = "readWrite") - public void shouldGetHgConfigEvenWhenItsEmpty() throws URISyntaxException { + public void shouldGetHgConfigEvenWhenItsEmpty() throws URISyntaxException, UnsupportedEncodingException { when(repositoryHandler.getConfig()).thenReturn(null); MockHttpResponse response = get(); @@ -110,7 +111,7 @@ public class HgConfigResourceTest { @Test @SubjectAware(username = "readOnly") - public void shouldGetHgConfigWithoutUpdateLink() throws URISyntaxException { + public void shouldGetHgConfigWithoutUpdateLink() throws URISyntaxException, UnsupportedEncodingException { MockHttpResponse response = get(); assertEquals(HttpServletResponse.SC_OK, response.getStatus()); diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigResourceTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigResourceTest.java index 3077bb34f3..f7ccf039b2 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigResourceTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigResourceTest.java @@ -16,14 +16,14 @@ import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.SvnConfig; import sonia.scm.repository.SvnRepositoryHandler; import sonia.scm.web.SvnVndMediaType; import javax.servlet.http.HttpServletResponse; -import java.io.File; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; @@ -98,7 +98,7 @@ public class SvnConfigResourceTest { @Test @SubjectAware(username = "readOnly") - public void shouldGetSvnConfigWithoutUpdateLink() throws URISyntaxException { + public void shouldGetSvnConfigWithoutUpdateLink() throws URISyntaxException, UnsupportedEncodingException { MockHttpResponse response = get(); assertEquals(HttpServletResponse.SC_OK, response.getStatus()); diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index c83cea36d5..93db9005b2 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -114,12 +114,6 @@ <version>${jackson.version}</version> </dependency> - <dependency> - <groupId>javax</groupId> - <artifactId>javaee-api</artifactId> - <version>7.0</version> - </dependency> - <!-- rest api --> <dependency> @@ -158,6 +152,13 @@ <version>${resteasy.version}</version> </dependency> + <dependency> + <groupId>javax.el</groupId> + <artifactId>javax.el-api</artifactId> + <version>3.0.1-b06</version> + <scope>provided</scope> + </dependency> + <!-- injection --> <dependency> @@ -561,7 +562,6 @@ <selenium.version>2.53.1</selenium.version> <wagon.version>1.0</wagon.version> <mustache.version>0.8.17</mustache.version> - <resteasy.version>3.1.4.Final</resteasy.version> <jackson.version>2.8.9</jackson.version> <netbeans.hint.deploy.server>Tomcat</netbeans.hint.deploy.server> <sonar.issue.ignore.multicriteria>e1</sonar.issue.ignore.multicriteria> diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java index 033824cbea..c058e06086 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java @@ -18,6 +18,7 @@ import sonia.scm.web.VndMediaType; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; @@ -68,7 +69,7 @@ public class ConfigResourceTest { @Test @SubjectAware(username = "readOnly") - public void shouldGetGlobalConfig() throws URISyntaxException { + public void shouldGetGlobalConfig() throws URISyntaxException, UnsupportedEncodingException { MockHttpRequest request = MockHttpRequest.get("/" + ConfigResource.CONFIG_PATH_V2); MockHttpResponse response = new MockHttpResponse(); dispatcher.invoke(request, response); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java index 646e9d0839..3e2d0f9663 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java @@ -24,6 +24,7 @@ import sonia.scm.web.VndMediaType; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; @@ -100,7 +101,7 @@ public class GroupRootResourceTest { } @Test - public void shouldGetGroup() throws URISyntaxException { + public void shouldGetGroup() throws URISyntaxException, UnsupportedEncodingException { Group group = createDummyGroup(); when(groupManager.get("admin")).thenReturn(group); @@ -305,7 +306,7 @@ public class GroupRootResourceTest { } @Test - public void shouldGetAll() throws URISyntaxException { + public void shouldGetAll() throws URISyntaxException, UnsupportedEncodingException { MockHttpRequest request = MockHttpRequest.get("/" + GroupRootResource.GROUPS_PATH_V2); MockHttpResponse response = new MockHttpResponse(); @@ -317,7 +318,7 @@ public class GroupRootResourceTest { } @Test - public void shouldGetPermissionLink() throws URISyntaxException { + public void shouldGetPermissionLink() throws URISyntaxException, UnsupportedEncodingException { MockHttpRequest request = MockHttpRequest.get("/" + GroupRootResource.GROUPS_PATH_V2 + "admin"); MockHttpResponse response = new MockHttpResponse(); @@ -329,7 +330,7 @@ public class GroupRootResourceTest { } @Test - public void shouldGetPermissions() throws URISyntaxException { + public void shouldGetPermissions() throws URISyntaxException, UnsupportedEncodingException { when(permissionAssigner.readPermissionsForGroup("admin")).thenReturn(singletonList(new PermissionDescriptor("something:*"))); MockHttpRequest request = MockHttpRequest.get("/" + GroupRootResource.GROUPS_PATH_V2 + "admin/permissions"); MockHttpResponse response = new MockHttpResponse(); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java index 052a059959..d83cea50a0 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java @@ -22,6 +22,7 @@ import sonia.scm.user.UserManager; import sonia.scm.web.VndMediaType; import javax.servlet.http.HttpServletResponse; +import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; @@ -78,7 +79,7 @@ public class MeResourceTest { } @Test - public void shouldReturnCurrentlyAuthenticatedUser() throws URISyntaxException { + public void shouldReturnCurrentlyAuthenticatedUser() throws URISyntaxException, UnsupportedEncodingException { applyUserToSubject(originalUser); MockHttpRequest request = MockHttpRequest.get("/" + MeResource.ME_PATH_V2); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java index 2795562b14..8dd451c556 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; +import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.inject.util.Providers; import de.otto.edison.hal.HalRepresentation; @@ -36,6 +37,7 @@ import sonia.scm.repository.RepositoryPermission; import sonia.scm.web.VndMediaType; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; @@ -214,7 +216,12 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { .expectedResponseStatus(200) .path(PATH_OF_ALL_PERMISSIONS + expectedPermission.getName()) .responseValidator((response) -> { - String body = response.getContentAsString(); + String body = null; + try { + body = response.getContentAsString(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } ObjectMapper mapper = new ObjectMapper(); try { RepositoryPermissionDto actualRepositoryPermissionDto = mapper.readValue(body, RepositoryPermissionDto.class); @@ -268,13 +275,21 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { assertExpectedRequest(requestPOSTPermission .content("{\"name\" : \"" + newPermission.getName() + "\" , \"verbs\" : [\"read\",\"pull\",\"push\"], \"groupPermission\" : true}") .expectedResponseStatus(201) - .responseValidator(response -> assertThat(response.getContentAsString()) + .responseValidator(response -> assertThat(getContentAsString(response)) .as("POST response has no body") .isBlank()) ); assertGettingExpectedPermissions(expectedPermissions, PERMISSION_WRITE); } + private String getContentAsString(MockHttpResponse response) { + try { + return response.getContentAsString(); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("could not get content from response", e); + } + } + @Test public void shouldNotAddExistingPermission() throws URISyntaxException { createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_WRITE); @@ -296,7 +311,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { .content("{\"name\" : \"" + modifiedPermission.getName() + "\" , \"verbs\" : [\"*\"], \"groupPermission\" : false}") .path(PATH_OF_ALL_PERMISSIONS + modifiedPermission.getName()) .expectedResponseStatus(204) - .responseValidator(response -> assertThat(response.getContentAsString()) + .responseValidator(response -> assertThat(getContentAsString(response)) .as("PUT response has no body") .isBlank()) ); @@ -312,7 +327,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { assertExpectedRequest(requestDELETEPermission .path(PATH_OF_ALL_PERMISSIONS + deletedPermission.getName()) .expectedResponseStatus(204) - .responseValidator(response -> assertThat(response.getContentAsString()) + .responseValidator(response -> assertThat(getContentAsString(response)) .as("DELETE response has no body") .isBlank()) ); @@ -327,7 +342,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { assertExpectedRequest(requestDELETEPermission .path(PATH_OF_ALL_PERMISSIONS + deletedPermission.getName()) .expectedResponseStatus(204) - .responseValidator(response -> assertThat(response.getContentAsString()) + .responseValidator(response -> assertThat(getContentAsString(response)) .as("DELETE response has no body") .isBlank()) ); @@ -335,7 +350,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { assertExpectedRequest(requestDELETEPermission .path(PATH_OF_ALL_PERMISSIONS + deletedPermission.getName()) .expectedResponseStatus(204) - .responseValidator(response -> assertThat(response.getContentAsString()) + .responseValidator(response -> assertThat(getContentAsString(response)) .as("DELETE response has no body") .isBlank()) ); @@ -346,7 +361,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { assertExpectedRequest(requestGETAllPermissions .expectedResponseStatus(200) .responseValidator((response) -> { - String body = response.getContentAsString(); + String body = getContentAsString(response); ObjectMapper mapper = new ObjectMapper(); try { HalRepresentation halRepresentation = mapper.readValue(body, HalRepresentation.class); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java index bf4366f0b2..032b79d2b7 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java @@ -27,6 +27,7 @@ import sonia.scm.web.VndMediaType; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; @@ -120,7 +121,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase { } @Test - public void shouldFindExistingRepository() throws URISyntaxException { + public void shouldFindExistingRepository() throws URISyntaxException, UnsupportedEncodingException { mockRepository("space", "repo"); MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo"); @@ -133,7 +134,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase { } @Test - public void shouldMapProperties() throws URISyntaxException { + public void shouldMapProperties() throws URISyntaxException, UnsupportedEncodingException { Repository repository = mockRepository("space", "repo"); repository.setProperty("testKey", "testValue"); @@ -146,7 +147,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase { } @Test - public void shouldGetAll() throws URISyntaxException { + public void shouldGetAll() throws URISyntaxException, UnsupportedEncodingException { PageResult<Repository> singletonPageResult = createSingletonPageResult(mockRepository("space", "repo")); when(repositoryManager.getPage(any(), eq(0), eq(10))).thenReturn(singletonPageResult); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UIRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UIRootResourceTest.java index 99a1435923..b2dafc8cfe 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UIRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UIRootResourceTest.java @@ -16,6 +16,7 @@ import sonia.scm.plugin.*; import sonia.scm.web.VndMediaType; import javax.servlet.http.HttpServletRequest; +import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.util.HashSet; @@ -87,7 +88,7 @@ public class UIRootResourceTest { } @Test - public void shouldReturnPlugin() throws URISyntaxException { + public void shouldReturnPlugin() throws URISyntaxException, UnsupportedEncodingException { mockPlugins(mockPlugin("awesome", "Awesome", createPluginResources("my/awesome.bundle.js"))); MockHttpRequest request = MockHttpRequest.get("/v2/ui/plugins/awesome"); @@ -101,7 +102,7 @@ public class UIRootResourceTest { } @Test - public void shouldReturnPlugins() throws URISyntaxException { + public void shouldReturnPlugins() throws URISyntaxException, UnsupportedEncodingException { mockPlugins( mockPlugin("awesome", "Awesome", createPluginResources("my/awesome.bundle.js")), mockPlugin("special", "Special", createPluginResources("my/special.bundle.js")) @@ -120,7 +121,7 @@ public class UIRootResourceTest { } @Test - public void shouldNotReturnPluginsWithoutResources() throws URISyntaxException { + public void shouldNotReturnPluginsWithoutResources() throws URISyntaxException, UnsupportedEncodingException { mockPlugins( mockPlugin("awesome", "Awesome", createPluginResources("my/awesome.bundle.js")), mockPlugin("special") diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java index 88142e4d50..1da540106a 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java @@ -26,6 +26,7 @@ import sonia.scm.user.UserManager; import sonia.scm.web.VndMediaType; import javax.servlet.http.HttpServletResponse; +import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; @@ -97,7 +98,7 @@ public class UserRootResourceTest { } @Test - public void shouldCreateFullResponseForAdmin() throws URISyntaxException { + public void shouldCreateFullResponseForAdmin() throws URISyntaxException, UnsupportedEncodingException { MockHttpRequest request = MockHttpRequest.get("/" + UserRootResource.USERS_PATH_V2 + "Neo"); MockHttpResponse response = new MockHttpResponse(); @@ -137,7 +138,7 @@ public class UserRootResourceTest { @Test @SubjectAware(username = "unpriv") - public void shouldCreateLimitedResponseForSimpleUser() throws URISyntaxException { + public void shouldCreateLimitedResponseForSimpleUser() throws URISyntaxException, UnsupportedEncodingException { MockHttpRequest request = MockHttpRequest.get("/" + UserRootResource.USERS_PATH_V2 + "Neo"); MockHttpResponse response = new MockHttpResponse(); @@ -331,7 +332,7 @@ public class UserRootResourceTest { } @Test - public void shouldCreatePageForOnePageOnly() throws URISyntaxException { + public void shouldCreatePageForOnePageOnly() throws URISyntaxException, UnsupportedEncodingException { PageResult<User> singletonPageResult = createSingletonPageResult(1); when(userManager.getPage(any(), eq(0), eq(10))).thenReturn(singletonPageResult); MockHttpRequest request = MockHttpRequest.get("/" + UserRootResource.USERS_PATH_V2); @@ -347,7 +348,7 @@ public class UserRootResourceTest { } @Test - public void shouldCreatePageForMultiplePages() throws URISyntaxException { + public void shouldCreatePageForMultiplePages() throws URISyntaxException, UnsupportedEncodingException { PageResult<User> singletonPageResult = createSingletonPageResult(3); when(userManager.getPage(any(), eq(1), eq(1))).thenReturn(singletonPageResult); MockHttpRequest request = MockHttpRequest.get("/" + UserRootResource.USERS_PATH_V2 + "?page=1&pageSize=1"); @@ -365,7 +366,7 @@ public class UserRootResourceTest { } @Test - public void shouldGetPermissionLink() throws URISyntaxException { + public void shouldGetPermissionLink() throws URISyntaxException, UnsupportedEncodingException { MockHttpRequest request = MockHttpRequest.get("/" + UserRootResource.USERS_PATH_V2 + "Neo"); MockHttpResponse response = new MockHttpResponse(); @@ -377,7 +378,7 @@ public class UserRootResourceTest { } @Test - public void shouldGetPermissions() throws URISyntaxException { + public void shouldGetPermissions() throws URISyntaxException, UnsupportedEncodingException { when(permissionAssigner.readPermissionsForUser("Neo")).thenReturn(singletonList(new PermissionDescriptor("something:*"))); MockHttpRequest request = MockHttpRequest.get("/" + UserRootResource.USERS_PATH_V2 + "Neo/permissions"); MockHttpResponse response = new MockHttpResponse(); From e5e4ae9ac99071eb2e1519f82d906da80b27659d Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Wed, 30 Jan 2019 13:43:29 +0100 Subject: [PATCH 042/112] fix gzip filter on resteasy 3.6.2 --- .../sonia/scm/filter/GZipResponseFilter.java | 52 +++++++++--- .../scm/filter/GZipResponseFilterTest.java | 81 +++++++++++++++++++ 2 files changed, 120 insertions(+), 13 deletions(-) create mode 100644 scm-core/src/test/java/sonia/scm/filter/GZipResponseFilterTest.java diff --git a/scm-core/src/main/java/sonia/scm/filter/GZipResponseFilter.java b/scm-core/src/main/java/sonia/scm/filter/GZipResponseFilter.java index 1fa525d4fd..92dd033af0 100644 --- a/scm-core/src/main/java/sonia/scm/filter/GZipResponseFilter.java +++ b/scm-core/src/main/java/sonia/scm/filter/GZipResponseFilter.java @@ -1,24 +1,50 @@ package sonia.scm.filter; -import lombok.extern.slf4j.Slf4j; -import sonia.scm.util.WebUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.container.ContainerResponseContext; -import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.ext.Provider; +import javax.ws.rs.ext.WriterInterceptor; +import javax.ws.rs.ext.WriterInterceptorContext; import java.io.IOException; +import java.io.OutputStream; +import java.util.Locale; import java.util.zip.GZIPOutputStream; @Provider -@Slf4j -public class GZipResponseFilter implements ContainerResponseFilter { - public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { - if (WebUtil.isGzipSupported(requestContext::getHeaderString)) { - log.trace("compress output with gzip"); - GZIPOutputStream wrappedResponse = new GZIPOutputStream(responseContext.getEntityStream()); - responseContext.getHeaders().add("Content-Encoding", "gzip"); - responseContext.setEntityStream(wrappedResponse); +public class GZipResponseFilter implements WriterInterceptor { + + private static final Logger LOG = LoggerFactory.getLogger(GZipResponseFilter.class); + + @Override + public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { + if (isGZipSupported(context)) { + LOG.trace("compress output with gzip"); + encodeWithGZip(context); + } else { + context.proceed(); } } + + private void encodeWithGZip(WriterInterceptorContext context) throws IOException { + context.getHeaders().remove(HttpHeaders.CONTENT_LENGTH); + context.getHeaders().add(HttpHeaders.CONTENT_ENCODING, "gzip"); + + OutputStream outputStream = context.getOutputStream(); + GZIPOutputStream compressedOutputStream = new GZIPOutputStream(outputStream); + context.setOutputStream(compressedOutputStream); + try { + context.proceed(); + } finally { + compressedOutputStream.finish(); + context.setOutputStream(outputStream); + } + } + + private boolean isGZipSupported(WriterInterceptorContext context) { + Object encoding = context.getHeaders().getFirst(HttpHeaders.ACCEPT_ENCODING); + return encoding != null && encoding.toString().toLowerCase(Locale.ENGLISH).contains("gzip"); + } } diff --git a/scm-core/src/test/java/sonia/scm/filter/GZipResponseFilterTest.java b/scm-core/src/test/java/sonia/scm/filter/GZipResponseFilterTest.java new file mode 100644 index 0000000000..b0a704c765 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/filter/GZipResponseFilterTest.java @@ -0,0 +1,81 @@ +package sonia.scm.filter; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.WriterInterceptorContext; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.zip.GZIPOutputStream; + +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class GZipResponseFilterTest { + + @Mock + private WriterInterceptorContext context; + + @Mock + private MultivaluedMap<String,Object> headers; + + private final GZipResponseFilter filter = new GZipResponseFilter(); + + @BeforeEach + void setUpContext() { + when(context.getHeaders()).thenReturn(headers); + } + + @Test + void shouldSkipGZipCompression() throws IOException { + when(headers.getFirst(HttpHeaders.ACCEPT_ENCODING)).thenReturn("deflate, br"); + + filter.aroundWriteTo(context); + + verifySkipped(); + } + + @Test + void shouldSkipGZipCompressionWithoutAcceptEncodingHeader() throws IOException { + filter.aroundWriteTo(context); + + verifySkipped(); + } + + private void verifySkipped() throws IOException { + verify(context, never()).getOutputStream(); + verify(context).proceed(); + } + + + @Nested + class AcceptGZipEncoding { + + @BeforeEach + void setUpContext() { + when(headers.getFirst(HttpHeaders.ACCEPT_ENCODING)).thenReturn("gzip, deflate, br"); + when(context.getOutputStream()).thenReturn(new ByteArrayOutputStream()); + } + + @Test + void shouldEncode() throws IOException { + filter.aroundWriteTo(context); + + verify(headers).remove(HttpHeaders.CONTENT_LENGTH); + verify(headers).add(HttpHeaders.CONTENT_ENCODING, "gzip"); + + verify(context).setOutputStream(any(GZIPOutputStream.class)); + verify(context, times(2)).setOutputStream(any(OutputStream.class)); + } + + } + + +} From 6ece9ea8b071824f9209da25b71704034c596b59 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Wed, 30 Jan 2019 13:48:32 +0100 Subject: [PATCH 043/112] update web-resources, spotter and tika to prevent CVE-2018-11761 and CVE-2018-17197 --- scm-webapp/pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 93db9005b2..dfaac3b4c0 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -256,19 +256,19 @@ <dependency> <groupId>com.github.sdorra</groupId> <artifactId>web-resources</artifactId> - <version>1.0.2</version> + <version>1.0.4</version> </dependency> <dependency> <groupId>com.github.sdorra</groupId> <artifactId>spotter-core</artifactId> - <version>1.1.0</version> + <version>1.2.1</version> </dependency> <dependency> <groupId>org.apache.tika</groupId> <artifactId>tika-core</artifactId> - <version>1.18</version> + <version>1.20</version> </dependency> <!-- test scope --> From f8f5237ad08366bec8343ea1e6f01949875f78e9 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Wed, 30 Jan 2019 14:44:38 +0100 Subject: [PATCH 044/112] fix usage of deprecated mockito classes --- .../api/v2/resources/GitConfigDtoToGitConfigMapperTest.java | 2 +- .../api/v2/resources/GitConfigToGitConfigDtoMapperTest.java | 2 +- .../test/java/sonia/scm/repository/GitHeadModifierTest.java | 2 +- .../test/java/sonia/scm/web/GitReceivePackFactoryTest.java | 2 +- .../v2/resources/HgConfigAutoConfigurationResourceTest.java | 2 +- .../api/v2/resources/HgConfigDtoToHgConfigMapperTest.java | 2 +- .../api/v2/resources/HgConfigInstallationsResourceTest.java | 2 +- .../v2/resources/HgConfigInstallationsToDtoMapperTest.java | 2 +- .../scm/api/v2/resources/HgConfigPackageResourceTest.java | 2 +- .../api/v2/resources/HgConfigPackagesToDtoMapperTest.java | 2 +- .../sonia/scm/api/v2/resources/HgConfigResourceTest.java | 2 +- .../api/v2/resources/HgConfigToHgConfigDtoMapperTest.java | 2 +- .../test/java/sonia/scm/web/HgHookCallbackServletTest.java | 2 +- .../src/test/java/sonia/scm/web/WireProtocolTest.java | 2 +- .../api/v2/resources/SvnConfigDtoToSvnConfigMapperTest.java | 2 +- .../api/v2/resources/SvnConfigToSvnConfigDtoMapperTest.java | 2 +- .../java/sonia/scm/repository/SvnRepositoryHandlerTest.java | 2 +- .../scm/api/rest/resources/AbstractManagerResourceTest.java | 2 +- .../v2/resources/RepositoryPermissionRootResourceTest.java | 2 +- .../scm/api/v2/resources/RepositoryRootResourceTest.java | 6 +++--- .../v2/resources/RepositoryToRepositoryDtoMapperTest.java | 2 +- .../sonia/scm/api/v2/resources/UserRootResourceTest.java | 6 +++--- .../test/java/sonia/scm/web/cgi/DefaultCGIExecutorTest.java | 2 +- 23 files changed, 27 insertions(+), 27 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapperTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapperTest.java index ed34db3008..6a05875aa9 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapperTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapperTest.java @@ -3,7 +3,7 @@ package sonia.scm.api.v2.resources; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.GitConfig; import static org.junit.Assert.*; diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java index 40cf36e8dd..ee17ecb34b 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java @@ -11,7 +11,7 @@ import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.GitConfig; import java.io.File; diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitHeadModifierTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitHeadModifierTest.java index 23b3110567..3362c8a22b 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitHeadModifierTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitHeadModifierTest.java @@ -40,7 +40,7 @@ import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import java.io.File; import java.io.IOException; diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitReceivePackFactoryTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitReceivePackFactoryTest.java index dc0822deba..4ed9d5a46a 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitReceivePackFactoryTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitReceivePackFactoryTest.java @@ -42,7 +42,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.GitConfig; import sonia.scm.repository.GitRepositoryHandler; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java index 4b66444bbe..1f88bfe665 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java @@ -14,7 +14,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.web.HgVndMediaType; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapperTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapperTest.java index 524e33e265..11dbd4638d 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapperTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapperTest.java @@ -3,7 +3,7 @@ package sonia.scm.api.v2.resources; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.HgConfig; import java.io.File; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java index 65b9c262cb..bcd9543d28 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java @@ -14,7 +14,7 @@ import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import javax.inject.Provider; import javax.servlet.http.HttpServletResponse; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapperTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapperTest.java index 7cae1d9f7e..80f8ec32b1 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapperTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsToDtoMapperTest.java @@ -6,7 +6,7 @@ import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import java.net.URI; import java.util.Arrays; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java index f1558b6efb..473ddfe4b4 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java @@ -17,7 +17,7 @@ import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.installer.HgPackage; import sonia.scm.installer.HgPackageReader; import sonia.scm.net.ahc.AdvancedHttpClient; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackagesToDtoMapperTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackagesToDtoMapperTest.java index c4431da6d5..0b5d7b14d0 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackagesToDtoMapperTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackagesToDtoMapperTest.java @@ -6,7 +6,7 @@ import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.installer.HgPackage; import sonia.scm.installer.HgPackages; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java index e9b96fd71b..764f999efa 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java @@ -16,7 +16,7 @@ import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.web.HgVndMediaType; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapperTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapperTest.java index 81c50f3d58..d4bc8be549 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapperTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigToHgConfigDtoMapperTest.java @@ -11,7 +11,7 @@ import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.HgConfig; import java.net.URI; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java index 7d74024630..efe9983951 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java @@ -8,7 +8,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import static org.mockito.Matchers.anyInt; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/WireProtocolTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/WireProtocolTest.java index 519dadfd6c..9237127c88 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/WireProtocolTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/WireProtocolTest.java @@ -37,7 +37,7 @@ import com.google.common.collect.Lists; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigDtoToSvnConfigMapperTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigDtoToSvnConfigMapperTest.java index 8ab947fbaf..27ca6d5635 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigDtoToSvnConfigMapperTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigDtoToSvnConfigMapperTest.java @@ -3,7 +3,7 @@ package sonia.scm.api.v2.resources; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.Compatibility; import sonia.scm.repository.SvnConfig; diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigToSvnConfigDtoMapperTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigToSvnConfigDtoMapperTest.java index 6bbff499e1..b48a959c83 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigToSvnConfigDtoMapperTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigToSvnConfigDtoMapperTest.java @@ -11,7 +11,7 @@ import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.Compatibility; import sonia.scm.repository.SvnConfig; diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java index 7b22e15c94..9b73c22c04 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java @@ -47,7 +47,7 @@ import java.io.IOException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; diff --git a/scm-webapp/src/test/java/sonia/scm/api/rest/resources/AbstractManagerResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/rest/resources/AbstractManagerResourceTest.java index 696174d6e0..fd9745be83 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/rest/resources/AbstractManagerResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/rest/resources/AbstractManagerResourceTest.java @@ -21,7 +21,7 @@ import java.util.Comparator; import static java.util.Collections.emptyList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java index 8dd451c556..33b30c5532 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java @@ -55,7 +55,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.mockito.Matchers.any; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java index 032b79d2b7..1f6ed6b3a7 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java @@ -42,9 +42,9 @@ import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyObject; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java index 8469e966c8..c5e2fb4670 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java @@ -23,7 +23,7 @@ import static java.util.stream.Stream.of; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java index 1da540106a..ffe1a746d5 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java @@ -36,8 +36,8 @@ import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; @@ -77,7 +77,7 @@ public class UserRootResourceTest { private User originalUser; @Before - public void prepareEnvironment() throws Exception { + public void prepareEnvironment() { initMocks(this); originalUser = createDummyUser("Neo"); when(userManager.create(userCaptor.capture())).thenAnswer(invocation -> invocation.getArguments()[0]); diff --git a/scm-webapp/src/test/java/sonia/scm/web/cgi/DefaultCGIExecutorTest.java b/scm-webapp/src/test/java/sonia/scm/web/cgi/DefaultCGIExecutorTest.java index 29c7dea358..5f95a171d2 100644 --- a/scm-webapp/src/test/java/sonia/scm/web/cgi/DefaultCGIExecutorTest.java +++ b/scm-webapp/src/test/java/sonia/scm/web/cgi/DefaultCGIExecutorTest.java @@ -3,7 +3,7 @@ package sonia.scm.web.cgi; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import javax.servlet.http.HttpServletRequest; From 8bf82213b8f4af9fef277f15e14061250a54258c Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Wed, 30 Jan 2019 14:48:37 +0100 Subject: [PATCH 045/112] remove unused imports --- scm-core/src/main/java/sonia/scm/HandlerBase.java | 1 - .../src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java | 1 - scm-core/src/main/java/sonia/scm/plugin/PluginLoader.java | 2 -- .../java/sonia/scm/repository/spi/MergeCommandRequest.java | 1 - .../main/java/sonia/scm/security/PermissionDescriptor.java | 1 - .../java/sonia/scm/security/StoredAssignedPermission.java | 2 -- .../sonia/scm/net/ahc/AdvancedHttpRequestWithBodyTest.java | 2 +- .../test/java/sonia/scm/security/DAORealmHelperTest.java | 1 - .../src/main/java/sonia/scm/store/JAXBDataStoreFactory.java | 3 --- scm-dao-xml/src/main/java/sonia/scm/xml/AbstractXmlDAO.java | 1 - scm-dao-xml/src/main/java/sonia/scm/xml/XmlStreams.java | 1 - scm-it/src/test/java/sonia/scm/it/RepositoriesITCase.java | 1 - scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java | 2 -- .../java/sonia/scm/api/v2/resources/GitConfigResource.java | 2 -- .../src/main/java/sonia/scm/web/GitRepositoryResolver.java | 1 - .../api/v2/resources/GitConfigToGitConfigDtoMapperTest.java | 1 - .../api/v2/resources/GitRepositoryConfigEnricherTest.java | 3 --- .../scm/repository/spi/AbstractGitCommandTestBase.java | 2 -- .../java/sonia/scm/repository/spi/GitBlameCommandTest.java | 2 -- .../java/sonia/scm/repository/spi/GitBrowseCommandTest.java | 2 -- .../java/sonia/scm/repository/spi/GitLogCommandTest.java | 2 -- .../main/java/sonia/scm/installer/AbstractHgInstaller.java | 4 +--- .../src/main/java/sonia/scm/repository/HgImportHandler.java | 1 - .../sonia/scm/repository/spi/javahg/HgFileviewCommand.java | 2 -- .../src/main/java/sonia/scm/web/HgPermissionFilter.java | 1 - .../api/v2/resources/HgConfigDtoToHgConfigMapperTest.java | 2 -- .../sonia/scm/api/v2/resources/HgConfigResourceTest.java | 1 - .../test/java/sonia/scm/api/v2/resources/HgConfigTests.java | 2 -- .../src/test/java/sonia/scm/web/HgPermissionFilterTest.java | 2 -- .../java/sonia/scm/repository/SvnRepositoryHandler.java | 1 - .../java/sonia/scm/repository/spi/SvnBrowseCommand.java | 2 -- .../api/v2/resources/SvnConfigToSvnConfigDtoMapperTest.java | 1 - .../java/sonia/scm/repository/SvnRepositoryHandlerTest.java | 4 ---- .../java/sonia/scm/repository/spi/SvnLogCommandTest.java | 2 -- .../scm/api/rest/resources/BrowserStreamingOutput.java | 2 -- .../sonia/scm/api/rest/resources/DiffStreamingOutput.java | 1 - .../java/sonia/scm/api/v2/resources/BranchRootResource.java | 1 - .../src/main/java/sonia/scm/api/v2/resources/ErrorDto.java | 1 - .../api/v2/resources/FileObjectToFileObjectDtoMapper.java | 3 --- .../sonia/scm/api/v2/resources/GroupCollectionResource.java | 1 - .../src/main/java/sonia/scm/api/v2/resources/GroupDto.java | 1 - .../sonia/scm/api/v2/resources/GroupDtoToGroupMapper.java | 2 -- .../api/v2/resources/PermissionCollectionToDtoMapper.java | 1 - .../scm/api/v2/resources/RepositoryCollectionResource.java | 1 - .../sonia/scm/api/v2/resources/RepositoryPermissionDto.java | 2 -- .../sonia/scm/api/v2/resources/UserCollectionResource.java | 1 - .../main/java/sonia/scm/boot/BootstrapContextListener.java | 3 --- scm-webapp/src/main/java/sonia/scm/filter/MDCFilter.java | 1 - .../java/sonia/scm/filter/PropagatePrincipleFilter.java | 2 -- .../src/main/java/sonia/scm/security/JwtAccessToken.java | 1 - .../web/filter/HttpProtocolServletAuthenticationFilter.java | 3 --- .../src/test/java/sonia/scm/api/v2/JsonFiltersTest.java | 1 - .../v2/resources/ChangesetCollectionToDtoMapperTest.java | 1 - .../scm/api/v2/resources/GroupToGroupDtoMapperTest.java | 1 - .../java/sonia/scm/api/v2/resources/MeDtoFactoryTest.java | 1 - .../java/sonia/scm/api/v2/resources/MeResourceTest.java | 1 - .../v2/resources/RepositoryPermissionRootResourceTest.java | 1 - .../sonia/scm/api/v2/resources/UserDtoToUserMapperTest.java | 1 - .../sonia/scm/api/v2/resources/UserRootResourceTest.java | 1 - .../src/test/java/sonia/scm/boot/RestartServletTest.java | 1 - .../test/java/sonia/scm/boot/ServletContextCleanerTest.java | 1 - .../sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java | 2 -- .../java/sonia/scm/plugin/MultiParentClassLoaderTest.java | 3 --- .../src/test/java/sonia/scm/schedule/QuartzTaskTest.java | 5 ++--- .../src/test/java/sonia/scm/security/BearerRealmTest.java | 6 ------ .../test/java/sonia/scm/security/SecureKeyResolverTest.java | 1 - .../test/java/sonia/scm/user/DefaultUserManagerTest.java | 3 --- .../src/test/java/sonia/scm/web/i18n/I18nServletTest.java | 2 -- 68 files changed, 4 insertions(+), 114 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/HandlerBase.java b/scm-core/src/main/java/sonia/scm/HandlerBase.java index a621f4f697..d960cc6107 100644 --- a/scm-core/src/main/java/sonia/scm/HandlerBase.java +++ b/scm-core/src/main/java/sonia/scm/HandlerBase.java @@ -36,7 +36,6 @@ package sonia.scm; //~--- JDK imports ------------------------------------------------------------ import java.io.Closeable; -import java.io.IOException; /** * The base class of all handlers. diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java b/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java index c80ae9b1c4..0723f44b6c 100644 --- a/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java +++ b/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java @@ -35,7 +35,6 @@ package sonia.scm.net.ahc; import com.google.common.base.Charsets; import com.google.common.base.Strings; -import com.google.common.collect.HashMultimap; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Multimap; diff --git a/scm-core/src/main/java/sonia/scm/plugin/PluginLoader.java b/scm-core/src/main/java/sonia/scm/plugin/PluginLoader.java index 0bf37054a8..2d65d1cc98 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/PluginLoader.java +++ b/scm-core/src/main/java/sonia/scm/plugin/PluginLoader.java @@ -35,8 +35,6 @@ package sonia.scm.plugin; //~--- non-JDK imports -------------------------------------------------------- -import com.google.inject.Module; - //~--- JDK imports ------------------------------------------------------------ import java.util.Collection; diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java b/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java index baf03a0aef..45dfc0b2b7 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/MergeCommandRequest.java @@ -5,7 +5,6 @@ import com.google.common.base.Objects; import com.google.common.base.Strings; import sonia.scm.Validateable; import sonia.scm.repository.Person; -import sonia.scm.util.Util; import java.io.Serializable; diff --git a/scm-core/src/main/java/sonia/scm/security/PermissionDescriptor.java b/scm-core/src/main/java/sonia/scm/security/PermissionDescriptor.java index a005583256..8d95131ee6 100644 --- a/scm-core/src/main/java/sonia/scm/security/PermissionDescriptor.java +++ b/scm-core/src/main/java/sonia/scm/security/PermissionDescriptor.java @@ -39,7 +39,6 @@ import com.google.common.base.Objects; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; diff --git a/scm-core/src/main/java/sonia/scm/security/StoredAssignedPermission.java b/scm-core/src/main/java/sonia/scm/security/StoredAssignedPermission.java index 4b2e46b665..903f86df90 100644 --- a/scm-core/src/main/java/sonia/scm/security/StoredAssignedPermission.java +++ b/scm-core/src/main/java/sonia/scm/security/StoredAssignedPermission.java @@ -34,8 +34,6 @@ package sonia.scm.security; //~--- JDK imports ------------------------------------------------------------ -import com.google.common.base.Objects; - import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBodyTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBodyTest.java index b7fa9ae84a..92ca488ddf 100644 --- a/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBodyTest.java +++ b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBodyTest.java @@ -36,7 +36,7 @@ import com.google.common.io.ByteSource; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; -import java.io.UnsupportedEncodingException; + import org.junit.Test; import static org.junit.Assert.*; import static org.hamcrest.Matchers.*; diff --git a/scm-core/src/test/java/sonia/scm/security/DAORealmHelperTest.java b/scm-core/src/test/java/sonia/scm/security/DAORealmHelperTest.java index af4bf37915..7ddeabd8ac 100644 --- a/scm-core/src/test/java/sonia/scm/security/DAORealmHelperTest.java +++ b/scm-core/src/test/java/sonia/scm/security/DAORealmHelperTest.java @@ -1,7 +1,6 @@ package sonia.scm.security; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.DisabledAccountException; import org.apache.shiro.authc.UnknownAccountException; diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBDataStoreFactory.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBDataStoreFactory.java index 5b5c00a298..579ef75b71 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBDataStoreFactory.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBDataStoreFactory.java @@ -37,9 +37,6 @@ package sonia.scm.store; import com.google.inject.Inject; import com.google.inject.Singleton; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import sonia.scm.SCMContextProvider; import sonia.scm.repository.RepositoryLocationResolver; import sonia.scm.security.KeyGenerator; diff --git a/scm-dao-xml/src/main/java/sonia/scm/xml/AbstractXmlDAO.java b/scm-dao-xml/src/main/java/sonia/scm/xml/AbstractXmlDAO.java index 1ce0508616..5b24096eb5 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/xml/AbstractXmlDAO.java +++ b/scm-dao-xml/src/main/java/sonia/scm/xml/AbstractXmlDAO.java @@ -38,7 +38,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.GenericDAO; import sonia.scm.ModelObject; -import sonia.scm.group.xml.XmlGroupDAO; import sonia.scm.store.ConfigurationStore; import java.util.Collection; diff --git a/scm-dao-xml/src/main/java/sonia/scm/xml/XmlStreams.java b/scm-dao-xml/src/main/java/sonia/scm/xml/XmlStreams.java index d812eedc35..4b3d9b0f28 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/xml/XmlStreams.java +++ b/scm-dao-xml/src/main/java/sonia/scm/xml/XmlStreams.java @@ -1,6 +1,5 @@ package sonia.scm.xml; -import com.google.common.base.Charsets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/scm-it/src/test/java/sonia/scm/it/RepositoriesITCase.java b/scm-it/src/test/java/sonia/scm/it/RepositoriesITCase.java index 3c67ca3dc3..c49a65bea2 100644 --- a/scm-it/src/test/java/sonia/scm/it/RepositoriesITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/RepositoriesITCase.java @@ -36,7 +36,6 @@ package sonia.scm.it; import org.apache.http.HttpStatus; import org.assertj.core.api.Assertions; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; diff --git a/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java b/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java index 9386f1d9c5..0a5693ad2e 100644 --- a/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java +++ b/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java @@ -5,10 +5,8 @@ import io.restassured.response.Response; import org.junit.Assert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.user.User; import sonia.scm.web.VndMediaType; -import java.net.ConnectException; import java.util.List; import java.util.Map; import java.util.function.Consumer; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java index e078b04b08..7cda4bc9d3 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java @@ -18,8 +18,6 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Response; -import static sonia.scm.ContextEntry.ContextBuilder.entity; - /** * RESTful Web Service Resource to manage the configuration of the git plugin. */ diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitRepositoryResolver.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitRepositoryResolver.java index a2114a1b6a..6db7d694d5 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitRepositoryResolver.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitRepositoryResolver.java @@ -35,7 +35,6 @@ package sonia.scm.web; //~--- non-JDK imports -------------------------------------------------------- -import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.inject.Inject; import org.eclipse.jgit.errors.RepositoryNotFoundException; diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java index ee17ecb34b..62fa8d33b4 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java @@ -14,7 +14,6 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.GitConfig; -import java.io.File; import java.net.URI; import static org.junit.Assert.assertEquals; diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricherTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricherTest.java index d2942d08a3..a1e349dd57 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricherTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitRepositoryConfigEnricherTest.java @@ -14,9 +14,6 @@ import org.mockito.junit.jupiter.MockitoExtension; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryManager; -import sonia.scm.repository.api.Command; -import sonia.scm.repository.api.RepositoryService; -import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.web.JsonEnricherContext; import sonia.scm.web.VndMediaType; diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java index 630236b20b..f2a4ed4954 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java @@ -35,10 +35,8 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- import org.junit.After; -import org.junit.Before; import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; import sonia.scm.repository.GitRepositoryConfig; -import sonia.scm.store.InMemoryConfigurationStore; import sonia.scm.store.InMemoryConfigurationStoreFactory; /** diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBlameCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBlameCommandTest.java index 817e4641dd..c8d260d503 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBlameCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBlameCommandTest.java @@ -35,11 +35,9 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- import org.junit.Test; -import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; import sonia.scm.repository.BlameLine; import sonia.scm.repository.BlameResult; import sonia.scm.repository.GitRepositoryConfig; -import sonia.scm.store.InMemoryConfigurationStoreFactory; import java.io.IOException; diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java index 2ff3c73420..1feceba652 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java @@ -32,11 +32,9 @@ package sonia.scm.repository.spi; import org.junit.Test; -import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; import sonia.scm.repository.GitRepositoryConfig; -import sonia.scm.store.InMemoryConfigurationStoreFactory; import java.io.IOException; import java.util.Collection; diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java index e2ab85d9a7..06e9b17fe7 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java @@ -36,10 +36,8 @@ package sonia.scm.repository.spi; import com.google.common.io.Files; import org.junit.Test; -import sonia.scm.event.ScmEventBus; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; -import sonia.scm.repository.ClearRepositoryCacheEvent; import sonia.scm.repository.GitRepositoryConfig; import sonia.scm.repository.Modifications; diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/AbstractHgInstaller.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/AbstractHgInstaller.java index 785aa399b1..27fdc7a296 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/AbstractHgInstaller.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/AbstractHgInstaller.java @@ -35,14 +35,12 @@ package sonia.scm.installer; //~--- non-JDK imports -------------------------------------------------------- -import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgRepositoryHandler; -import sonia.scm.util.IOUtil; //~--- JDK imports ------------------------------------------------------------ import java.io.File; -import java.io.IOException; + import sonia.scm.net.ahc.AdvancedHttpClient; /** diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgImportHandler.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgImportHandler.java index 4b6998f09a..b1d431c742 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgImportHandler.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgImportHandler.java @@ -39,7 +39,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.io.INIConfiguration; import sonia.scm.io.INIConfigurationReader; -import sonia.scm.io.INIConfigurationWriter; import sonia.scm.io.INISection; import sonia.scm.util.ValidationUtil; diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/HgFileviewCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/HgFileviewCommand.java index f351ffa572..0897a191a1 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/HgFileviewCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/javahg/HgFileviewCommand.java @@ -41,7 +41,6 @@ import com.aragost.javahg.internals.AbstractCommand; import com.aragost.javahg.internals.HgInputStream; import com.google.common.base.Strings; -import com.google.common.collect.Lists; import sonia.scm.repository.FileObject; import sonia.scm.repository.SubRepository; @@ -52,7 +51,6 @@ import java.io.IOException; import java.util.Deque; import java.util.LinkedList; -import java.util.List; /** * Mercurial command to list files of a repository. diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgPermissionFilter.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgPermissionFilter.java index 93b9699fc9..18b716b665 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgPermissionFilter.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgPermissionFilter.java @@ -44,7 +44,6 @@ import sonia.scm.web.filter.PermissionFilter; import sonia.scm.repository.HgRepositoryHandler; -import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import java.util.Set; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapperTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapperTest.java index 11dbd4638d..6e181f4886 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapperTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigDtoToHgConfigMapperTest.java @@ -6,8 +6,6 @@ import org.mockito.InjectMocks; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.HgConfig; -import java.io.File; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java index 764f999efa..e0253ad86a 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java @@ -23,7 +23,6 @@ import sonia.scm.web.HgVndMediaType; import javax.inject.Provider; import javax.servlet.http.HttpServletResponse; -import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigTests.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigTests.java index 84343cdf72..a3430aac43 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigTests.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigTests.java @@ -3,8 +3,6 @@ package sonia.scm.api.v2.resources; import sonia.scm.installer.HgPackage; import sonia.scm.repository.HgConfig; -import java.io.File; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgPermissionFilterTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgPermissionFilterTest.java index b3a4a0c2a4..f9bc77bbda 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgPermissionFilterTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgPermissionFilterTest.java @@ -48,8 +48,6 @@ import javax.servlet.http.HttpServletRequest; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.junit.Assert.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import static sonia.scm.web.WireProtocolRequestMockFactory.CMDS_HEADS_KNOWN_NODES; import static sonia.scm.web.WireProtocolRequestMockFactory.Namespace.BOOKMARKS; import static sonia.scm.web.WireProtocolRequestMockFactory.Namespace.PHASES; diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java index 86f99cd517..97698d7a77 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java @@ -55,7 +55,6 @@ import sonia.scm.logging.SVNKitLogger; import sonia.scm.plugin.Extension; import sonia.scm.repository.spi.HookEventFacade; import sonia.scm.repository.spi.SvnRepositoryServiceProvider; -import sonia.scm.store.ConfigurationStore; import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.util.Util; diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBrowseCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBrowseCommand.java index e2f58b593b..df266a11af 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBrowseCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBrowseCommand.java @@ -36,7 +36,6 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- import com.google.common.base.Strings; -import com.google.common.collect.Lists; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.tmatesoft.svn.core.SVNDirEntry; @@ -53,7 +52,6 @@ import sonia.scm.repository.SvnUtil; import sonia.scm.util.Util; import java.util.Collection; -import java.util.List; //~--- JDK imports ------------------------------------------------------------ diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigToSvnConfigDtoMapperTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigToSvnConfigDtoMapperTest.java index b48a959c83..07ead15322 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigToSvnConfigDtoMapperTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigToSvnConfigDtoMapperTest.java @@ -15,7 +15,6 @@ import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.Compatibility; import sonia.scm.repository.SvnConfig; -import java.io.File; import java.net.URI; import static org.junit.Assert.assertEquals; diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java index 9b73c22c04..7c8dc15407 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java @@ -32,14 +32,10 @@ package sonia.scm.repository; -import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.api.HookContextFactory; import sonia.scm.repository.spi.HookEventFacade; -import sonia.scm.store.ConfigurationStore; import sonia.scm.store.ConfigurationStoreFactory; import java.io.File; diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnLogCommandTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnLogCommandTest.java index f2511a9ad9..0cfeaa3a1c 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnLogCommandTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnLogCommandTest.java @@ -39,8 +39,6 @@ import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.Modifications; -import java.io.IOException; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/BrowserStreamingOutput.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/BrowserStreamingOutput.java index d2ce744c19..79b5dbc2ae 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/BrowserStreamingOutput.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/BrowserStreamingOutput.java @@ -6,8 +6,6 @@ import sonia.scm.repository.api.CatCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.util.IOUtil; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Response; import javax.ws.rs.core.StreamingOutput; import java.io.IOException; import java.io.OutputStream; diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/DiffStreamingOutput.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/DiffStreamingOutput.java index d177e05a5e..b7f994b967 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/DiffStreamingOutput.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/DiffStreamingOutput.java @@ -42,7 +42,6 @@ import sonia.scm.repository.api.RepositoryService; import sonia.scm.util.IOUtil; import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Response; import javax.ws.rs.core.StreamingOutput; import java.io.IOException; import java.io.OutputStream; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java index 658abbded8..71b1127ad8 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java @@ -26,7 +26,6 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; import java.io.IOException; -import java.util.List; import static sonia.scm.ContextEntry.ContextBuilder.entity; import static sonia.scm.NotFoundException.notFound; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java index bd889d5de5..d155fbede6 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java @@ -5,7 +5,6 @@ import lombok.Getter; import lombok.Setter; import sonia.scm.ContextEntry; -import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; import java.util.List; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java index 2432d5168c..2ec662ef9b 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java @@ -12,9 +12,6 @@ import sonia.scm.repository.SubRepository; import javax.inject.Inject; -import java.util.List; -import java.util.stream.Collectors; - import static de.otto.edison.hal.Link.link; @Mapper diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java index 4c111e6707..6c13dc33a5 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java @@ -10,7 +10,6 @@ import sonia.scm.group.GroupManager; import sonia.scm.web.VndMediaType; import javax.inject.Inject; -import javax.inject.Named; import javax.validation.Valid; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java index 760beab1da..eb174c69b6 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java @@ -6,7 +6,6 @@ import de.otto.edison.hal.Links; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.hibernate.validator.constraints.NotEmpty; import javax.validation.constraints.Pattern; import java.time.Instant; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDtoToGroupMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDtoToGroupMapper.java index be1aca5814..3812f700da 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDtoToGroupMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDtoToGroupMapper.java @@ -4,8 +4,6 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; import sonia.scm.group.Group; -import java.time.Instant; - @Mapper public abstract class GroupDtoToGroupMapper extends BaseDtoMapper { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionCollectionToDtoMapper.java index 87d1aeca9f..31269d468e 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionCollectionToDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionCollectionToDtoMapper.java @@ -1,7 +1,6 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.Links; -import org.mapstruct.Context; import sonia.scm.security.PermissionDescriptor; import sonia.scm.security.PermissionPermissions; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java index a9bd5c2424..e1e1260a4d 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java @@ -23,7 +23,6 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; -import static java.util.Arrays.asList; import static java.util.Collections.singletonList; public class RepositoryCollectionResource { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionDto.java index 09683db488..fe8c2c19b1 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionDto.java @@ -1,6 +1,5 @@ package sonia.scm.api.v2.resources; -import com.fasterxml.jackson.annotation.JsonInclude; import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.Links; import lombok.Getter; @@ -9,7 +8,6 @@ import lombok.Setter; import lombok.ToString; import org.hibernate.validator.constraints.NotEmpty; -import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; import java.util.Collection; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java index a4fe9adb94..a7442a2262 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java @@ -11,7 +11,6 @@ import sonia.scm.user.UserManager; import sonia.scm.web.VndMediaType; import javax.inject.Inject; -import javax.inject.Named; import javax.validation.Valid; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; diff --git a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextListener.java b/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextListener.java index afaa28bfe8..be5a1e7ac2 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextListener.java +++ b/scm-webapp/src/main/java/sonia/scm/boot/BootstrapContextListener.java @@ -33,12 +33,9 @@ package sonia.scm.boot; //~--- non-JDK imports -------------------------------------------------------- -import com.github.legman.Subscribe; - import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import com.google.common.io.Files; -import com.google.inject.servlet.GuiceFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/scm-webapp/src/main/java/sonia/scm/filter/MDCFilter.java b/scm-webapp/src/main/java/sonia/scm/filter/MDCFilter.java index b77a927a2d..fc52ef4eff 100644 --- a/scm-webapp/src/main/java/sonia/scm/filter/MDCFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/filter/MDCFilter.java @@ -42,7 +42,6 @@ import org.slf4j.MDC; import sonia.scm.SCMContext; import sonia.scm.security.DefaultKeyGenerator; -import sonia.scm.security.KeyGenerator; import sonia.scm.web.filter.HttpFilter; //~--- JDK imports ------------------------------------------------------------ diff --git a/scm-webapp/src/main/java/sonia/scm/filter/PropagatePrincipleFilter.java b/scm-webapp/src/main/java/sonia/scm/filter/PropagatePrincipleFilter.java index 508e804d1f..e7d020ae18 100644 --- a/scm-webapp/src/main/java/sonia/scm/filter/PropagatePrincipleFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/filter/PropagatePrincipleFilter.java @@ -51,8 +51,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import static sonia.scm.api.v2.resources.ScmPathInfo.REST_API_PATH; - //~--- JDK imports ------------------------------------------------------------ /** diff --git a/scm-webapp/src/main/java/sonia/scm/security/JwtAccessToken.java b/scm-webapp/src/main/java/sonia/scm/security/JwtAccessToken.java index 64e26405a1..5b895a34fa 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/JwtAccessToken.java +++ b/scm-webapp/src/main/java/sonia/scm/security/JwtAccessToken.java @@ -35,7 +35,6 @@ import io.jsonwebtoken.Claims; import java.util.Collections; import java.util.Date; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; diff --git a/scm-webapp/src/main/java/sonia/scm/web/filter/HttpProtocolServletAuthenticationFilter.java b/scm-webapp/src/main/java/sonia/scm/web/filter/HttpProtocolServletAuthenticationFilter.java index 77683bd6be..e58945a346 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/filter/HttpProtocolServletAuthenticationFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/web/filter/HttpProtocolServletAuthenticationFilter.java @@ -17,9 +17,6 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Set; -import static sonia.scm.util.HttpUtil.AUTHENTICATION_REALM; -import static sonia.scm.util.HttpUtil.HEADER_WWW_AUTHENTICATE; - @Priority(Filters.PRIORITY_AUTHENTICATION) @WebElement(value = HttpProtocolServlet.PATTERN) public class HttpProtocolServletAuthenticationFilter extends AuthenticationFilter { diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/JsonFiltersTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/JsonFiltersTest.java index b60775a73b..8bdbacafc2 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/JsonFiltersTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/JsonFiltersTest.java @@ -3,7 +3,6 @@ package sonia.scm.api.v2; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; -import com.google.common.collect.Lists; import com.google.common.io.Resources; import org.junit.Test; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetCollectionToDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetCollectionToDtoMapperTest.java index 69695279e6..87ee5943db 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetCollectionToDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetCollectionToDtoMapperTest.java @@ -1,6 +1,5 @@ package sonia.scm.api.v2.resources; -import org.assertj.core.api.Assertions; import org.junit.Test; import sonia.scm.PageResult; import sonia.scm.repository.Changeset; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupToGroupDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupToGroupDtoMapperTest.java index b681dff21f..7965d949c4 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupToGroupDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupToGroupDtoMapperTest.java @@ -11,7 +11,6 @@ import org.mockito.InjectMocks; import sonia.scm.group.Group; import java.net.URI; -import java.net.URISyntaxException; import java.util.stream.IntStream; import static java.util.stream.Collectors.toList; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeDtoFactoryTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeDtoFactoryTest.java index 138387938b..c85a803b00 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeDtoFactoryTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeDtoFactoryTest.java @@ -15,7 +15,6 @@ import org.mockito.quality.Strictness; import sonia.scm.group.GroupNames; import sonia.scm.user.User; import sonia.scm.user.UserManager; -import sonia.scm.user.UserPermissions; import sonia.scm.user.UserTestData; import java.net.URI; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java index d83cea50a0..cd2a172c1b 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java @@ -28,7 +28,6 @@ import java.net.URISyntaxException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import static org.mockito.MockitoAnnotations.initMocks; import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java index 33b30c5532..4472acb2c5 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java @@ -4,7 +4,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; -import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.inject.util.Providers; import de.otto.edison.hal.HalRepresentation; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserDtoToUserMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserDtoToUserMapperTest.java index 19f247b3b2..552009b73f 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserDtoToUserMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserDtoToUserMapperTest.java @@ -10,7 +10,6 @@ import sonia.scm.user.User; import java.time.Instant; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; public class UserDtoToUserMapperTest { diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java index ffe1a746d5..4047dfadd2 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java @@ -14,7 +14,6 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.Spy; import sonia.scm.ContextEntry; import sonia.scm.NotFoundException; import sonia.scm.PageResult; diff --git a/scm-webapp/src/test/java/sonia/scm/boot/RestartServletTest.java b/scm-webapp/src/test/java/sonia/scm/boot/RestartServletTest.java index b8b538c82b..eac4a12340 100644 --- a/scm-webapp/src/test/java/sonia/scm/boot/RestartServletTest.java +++ b/scm-webapp/src/test/java/sonia/scm/boot/RestartServletTest.java @@ -2,7 +2,6 @@ package sonia.scm.boot; import com.github.legman.Subscribe; import com.google.common.base.Charsets; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; diff --git a/scm-webapp/src/test/java/sonia/scm/boot/ServletContextCleanerTest.java b/scm-webapp/src/test/java/sonia/scm/boot/ServletContextCleanerTest.java index a26cf3b215..c9d8c594b4 100644 --- a/scm-webapp/src/test/java/sonia/scm/boot/ServletContextCleanerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/boot/ServletContextCleanerTest.java @@ -13,7 +13,6 @@ import java.util.Enumeration; import java.util.Set; import java.util.Vector; -import static org.junit.Assert.*; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; diff --git a/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java index a60c884b64..314dcdbeff 100644 --- a/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java +++ b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java @@ -49,8 +49,6 @@ import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.config.ScmConfiguration; -import static org.hamcrest.Matchers.*; - import static org.junit.Assert.*; import static org.mockito.Mockito.*; diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/MultiParentClassLoaderTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/MultiParentClassLoaderTest.java index ae65f5c1ae..df31977de1 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/MultiParentClassLoaderTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/MultiParentClassLoaderTest.java @@ -29,9 +29,6 @@ package sonia.scm.plugin; -import com.google.common.base.Enums; -import com.google.common.collect.Iterables; -import com.google.common.collect.Iterators; import java.io.IOException; import java.net.URL; import java.util.Arrays; diff --git a/scm-webapp/src/test/java/sonia/scm/schedule/QuartzTaskTest.java b/scm-webapp/src/test/java/sonia/scm/schedule/QuartzTaskTest.java index efaeb702fe..baf4c659cc 100644 --- a/scm-webapp/src/test/java/sonia/scm/schedule/QuartzTaskTest.java +++ b/scm-webapp/src/test/java/sonia/scm/schedule/QuartzTaskTest.java @@ -32,12 +32,11 @@ package sonia.scm.schedule; import org.junit.Test; -import static org.junit.Assert.*; + import static org.mockito.Mockito.*; -import static org.hamcrest.Matchers.*; + import org.junit.Before; import org.junit.runner.RunWith; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.quartz.JobKey; diff --git a/scm-webapp/src/test/java/sonia/scm/security/BearerRealmTest.java b/scm-webapp/src/test/java/sonia/scm/security/BearerRealmTest.java index 5c7aa08f37..c2d75358fd 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/BearerRealmTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/BearerRealmTest.java @@ -33,18 +33,13 @@ package sonia.scm.security; import com.google.common.collect.ImmutableSet; import org.apache.shiro.authc.AuthenticationInfo; -import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UsernamePasswordToken; -import org.junit.Ignore; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Answers; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.stubbing.Answer; import java.util.HashMap; import java.util.Set; @@ -52,7 +47,6 @@ import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; diff --git a/scm-webapp/src/test/java/sonia/scm/security/SecureKeyResolverTest.java b/scm-webapp/src/test/java/sonia/scm/security/SecureKeyResolverTest.java index cce3fea2b1..f59991f2cc 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/SecureKeyResolverTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/SecureKeyResolverTest.java @@ -47,7 +47,6 @@ import sonia.scm.store.ConfigurationEntryStoreFactory; import java.util.Random; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.in; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; diff --git a/scm-webapp/src/test/java/sonia/scm/user/DefaultUserManagerTest.java b/scm-webapp/src/test/java/sonia/scm/user/DefaultUserManagerTest.java index 8e261b75cc..ab31d751fd 100644 --- a/scm-webapp/src/test/java/sonia/scm/user/DefaultUserManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/user/DefaultUserManagerTest.java @@ -45,9 +45,6 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; import sonia.scm.NotFoundException; -import sonia.scm.repository.InitialRepositoryLocationResolver; -import sonia.scm.repository.RepositoryDAO; -import sonia.scm.repository.RepositoryLocationResolver; import sonia.scm.store.JAXBConfigurationStoreFactory; import sonia.scm.user.xml.XmlUserDAO; diff --git a/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java b/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java index a912f738e2..7679056758 100644 --- a/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java +++ b/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java @@ -37,8 +37,6 @@ import java.util.Enumeration; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.Silent.class) From 9cc9e0937e36110d47872b60cdeb624ad2d07294 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Wed, 30 Jan 2019 15:23:34 +0100 Subject: [PATCH 046/112] update jackson to version 2.9.8 --- pom.xml | 2 +- .../sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index cae4aa9bb6..a40f09c441 100644 --- a/pom.xml +++ b/pom.xml @@ -829,7 +829,7 @@ <resteasy.version>3.6.2.Final</resteasy.version> <jersey-client.version>1.19.4</jersey-client.version> <enunciate.version>2.11.1</enunciate.version> - <jackson.version>2.8.6</jackson.version> + <jackson.version>2.9.8</jackson.version> <guice.version>4.0</guice.version> <jaxb.version>2.3.0</jaxb.version> diff --git a/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java index 314dcdbeff..399f20cd3f 100644 --- a/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java +++ b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java @@ -41,6 +41,7 @@ import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.io.ByteSource; +import org.hamcrest.Matchers; import org.junit.Test; import org.junit.runner.RunWith; @@ -134,7 +135,7 @@ public class DefaultAdvancedHttpResponseTest connection, 200, "OK"); Multimap<String, String> headers = response.getHeaders(); - assertThat(headers.get("Test"), contains("One", "Two")); + assertThat(headers.get("Test"), Matchers.contains("One", "Two")); assertTrue(headers.get("Test-2").isEmpty()); } @@ -142,8 +143,7 @@ public class DefaultAdvancedHttpResponseTest /** Field description */ private final DefaultAdvancedHttpClient client = - new DefaultAdvancedHttpClient(new ScmConfiguration(), - new HashSet<ContentTransformer>(), new SSLContextProvider()); + new DefaultAdvancedHttpClient(new ScmConfiguration(), new HashSet<>(), new SSLContextProvider()); /** Field description */ @Mock From 470ef7aaf464fb1425421318d5a9492c8afd55fe Mon Sep 17 00:00:00 2001 From: Mohamed Karray <mohamed.karray.sr@gmail.com> Date: Wed, 30 Jan 2019 17:57:58 +0100 Subject: [PATCH 047/112] move the ChangesetDtoMapper to core --- .../resources/ChangesetToChangesetDtoMapper.java | 14 ++++++++++++++ ...a => DefaultChangesetToChangesetDtoMapper.java} | 2 +- .../sonia/scm/api/v2/resources/MapperModule.java | 2 +- .../api/v2/resources/BranchRootResourceTest.java | 2 +- .../ChangesetCollectionToDtoMapperTest.java | 3 +-- .../v2/resources/ChangesetRootResourceTest.java | 2 +- .../api/v2/resources/FileHistoryResourceTest.java | 2 +- .../api/v2/resources/IncomingRootResourceTest.java | 2 +- 8 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java rename scm-webapp/src/main/java/sonia/scm/api/v2/resources/{ChangesetToChangesetDtoMapper.java => DefaultChangesetToChangesetDtoMapper.java} (94%) diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java new file mode 100644 index 0000000000..cd7d7ecebe --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java @@ -0,0 +1,14 @@ +package sonia.scm.api.v2.resources; + +import org.mapstruct.Context; +import org.mapstruct.Mapping; +import sonia.scm.repository.Changeset; +import sonia.scm.repository.Repository; + +public interface ChangesetToChangesetDtoMapper { + + @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes + ChangesetDto map(Changeset changeset, @Context Repository repository); + + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DefaultChangesetToChangesetDtoMapper.java similarity index 94% rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java rename to scm-webapp/src/main/java/sonia/scm/api/v2/resources/DefaultChangesetToChangesetDtoMapper.java index 219062d320..924209d3da 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DefaultChangesetToChangesetDtoMapper.java @@ -23,7 +23,7 @@ import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Links.linkingTo; @Mapper -public abstract class ChangesetToChangesetDtoMapper extends LinkAppenderMapper implements InstantAttributeMapper { +public abstract class DefaultChangesetToChangesetDtoMapper extends LinkAppenderMapper implements InstantAttributeMapper , ChangesetToChangesetDtoMapper { @Inject private RepositoryServiceFactory serviceFactory; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java index 4cf66f7b28..e5142dc9fa 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java @@ -28,7 +28,7 @@ public class MapperModule extends AbstractModule { bind(PermissionDtoToPermissionMapper.class).to(Mappers.getMapper(PermissionDtoToPermissionMapper.class).getClass()); bind(RepositoryPermissionToRepositoryPermissionDtoMapper.class).to(Mappers.getMapper(RepositoryPermissionToRepositoryPermissionDtoMapper.class).getClass()); - bind(ChangesetToChangesetDtoMapper.class).to(Mappers.getMapper(ChangesetToChangesetDtoMapper.class).getClass()); + bind(ChangesetToChangesetDtoMapper.class).to(Mappers.getMapper(DefaultChangesetToChangesetDtoMapper.class).getClass()); bind(ChangesetToParentDtoMapper.class).to(Mappers.getMapper(ChangesetToParentDtoMapper.class).getClass()); bind(TagToTagDtoMapper.class).to(Mappers.getMapper(TagToTagDtoMapper.class).getClass()); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java index 4994c11b08..9216922e19 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java @@ -82,7 +82,7 @@ public class BranchRootResourceTest extends RepositoryTestBase { @InjectMocks - private ChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper; + private DefaultChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper; private final Subject subject = mock(Subject.class); private final ThreadState subjectThreadState = new SubjectThreadState(subject); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetCollectionToDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetCollectionToDtoMapperTest.java index 69695279e6..7653fbe122 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetCollectionToDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetCollectionToDtoMapperTest.java @@ -1,6 +1,5 @@ package sonia.scm.api.v2.resources; -import org.assertj.core.api.Assertions; import org.junit.Test; import sonia.scm.PageResult; import sonia.scm.repository.Changeset; @@ -17,7 +16,7 @@ public class ChangesetCollectionToDtoMapperTest { public static final Repository REPOSITORY = new Repository("", "git", "space", "name"); public static final Changeset CHANGESET = new Changeset(); - private final ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper = mock(ChangesetToChangesetDtoMapper.class); + private final DefaultChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper = mock(DefaultChangesetToChangesetDtoMapperImpl.class); private final ChangesetCollectionToDtoMapper changesetCollectionToDtoMapper = new ChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, ResourceLinksMock.createMock(URI.create("/"))); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java index d5c0f91f81..952c8504f6 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java @@ -65,7 +65,7 @@ public class ChangesetRootResourceTest extends RepositoryTestBase { private ChangesetCollectionToDtoMapper changesetCollectionToDtoMapper; @InjectMocks - private ChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper; + private DefaultChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper; private ChangesetRootResource changesetRootResource; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java index 52c9a434c0..a8b3c15158 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java @@ -66,7 +66,7 @@ public class FileHistoryResourceTest extends RepositoryTestBase { private FileHistoryCollectionToDtoMapper fileHistoryCollectionToDtoMapper; @InjectMocks - private ChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper; + private DefaultChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper; private FileHistoryRootResource fileHistoryRootResource; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IncomingRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IncomingRootResourceTest.java index e4495a0455..b965c2f2c3 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IncomingRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IncomingRootResourceTest.java @@ -74,7 +74,7 @@ public class IncomingRootResourceTest extends RepositoryTestBase { private IncomingChangesetCollectionToDtoMapper incomingChangesetCollectionToDtoMapper; @InjectMocks - private ChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper; + private DefaultChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper; private IncomingRootResource incomingRootResource; From e7a96cfe8f55866b6bdfbd3a6bbe0f2c6814ce1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= <maren.suewer@cloudogu.com> Date: Thu, 31 Jan 2019 09:41:55 +0000 Subject: [PATCH 048/112] Close branch bugfix/user_editing From 225fb0a705fdfb92c601e7db70d1b07ff41b31dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= <rene.pfeuffer@cloudogu.com> Date: Thu, 31 Jan 2019 11:39:16 +0100 Subject: [PATCH 049/112] Validate groups and users in backend, too --- .../sonia/scm/api/v2/resources/ConfigDto.java | 2 ++ .../scm/api/v2/resources/ConfigResource.java | 3 ++- .../scm/api/v2/resources/NoBlankStrings.java | 26 +++++++++++++++++++ .../v2/resources/NoBlankStringsValidator.java | 23 ++++++++++++++++ 4 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/NoBlankStrings.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/NoBlankStringsValidator.java diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDto.java index 4c9620564b..f77823eaac 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDto.java @@ -23,7 +23,9 @@ public class ConfigDto extends HalRepresentation { private boolean disableGroupingGrid; private String dateFormat; private boolean anonymousAccessEnabled; + @NoBlankStrings private Set<String> adminGroups; + @NoBlankStrings private Set<String> adminUsers; private String baseUrl; private boolean forceBaseUrl; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java index bf3a11fb9c..c646dceab4 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java @@ -9,6 +9,7 @@ import sonia.scm.util.ScmConfigurationUtil; import sonia.scm.web.VndMediaType; import javax.inject.Inject; +import javax.validation.Valid; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.PUT; @@ -71,7 +72,7 @@ public class ConfigResource { @ResponseCode(code = 500, condition = "internal server error") }) @TypeHint(TypeHint.NO_CONTENT.class) - public Response update(ConfigDto configDto) { + public Response update(@Valid ConfigDto configDto) { // This *could* be moved to ScmConfiguration or ScmConfigurationUtil classes. // But to where to check? load() or store()? Leave it for now, SCMv1 legacy that can be cleaned up later. diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NoBlankStrings.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NoBlankStrings.java new file mode 100644 index 0000000000..ba5e20ffbd --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NoBlankStrings.java @@ -0,0 +1,26 @@ +package sonia.scm.api.v2.resources; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE}) +@Retention(RUNTIME) +@Constraint(validatedBy = NoBlankStringsValidator.class) +@Documented +public @interface NoBlankStrings { + + String message() default "collection must not contain empty strings"; + + Class<?>[] groups() default {}; + + Class<? extends Payload>[] payload() default {}; +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NoBlankStringsValidator.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NoBlankStringsValidator.java new file mode 100644 index 0000000000..6bae44164e --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NoBlankStringsValidator.java @@ -0,0 +1,23 @@ +package sonia.scm.api.v2.resources; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.Collection; + +public class NoBlankStringsValidator implements ConstraintValidator<NoBlankStrings, Collection> { + + @Override + public void initialize(NoBlankStrings constraintAnnotation) { + } + + @Override + public boolean isValid(Collection object, ConstraintValidatorContext constraintContext) { + if ( object == null || object.isEmpty()) { + return true; + } + return object.stream() + .map(x -> x.toString()) + .map(s -> ((String) s).trim()) + .noneMatch(s -> ((String) s).isEmpty()); + } +} From ddf89e45550ad4af4198a15cfa89a046089b4af9 Mon Sep 17 00:00:00 2001 From: Iwan Schindler <iwan.schindler@cloudogu.com> Date: Thu, 31 Jan 2019 11:42:02 +0100 Subject: [PATCH 050/112] Fixed spelling, typos in de/en language files --- scm-ui/public/locales/de/commons.json | 8 ++++---- scm-ui/public/locales/de/config.json | 18 +++++++++--------- scm-ui/public/locales/de/groups.json | 10 +++++----- scm-ui/public/locales/de/repos.json | 8 ++++---- scm-ui/public/locales/de/users.json | 18 +++++++++--------- scm-ui/public/locales/en/commons.json | 8 ++++---- scm-ui/public/locales/en/config.json | 12 ++++++------ scm-ui/public/locales/en/groups.json | 2 +- scm-ui/public/locales/en/repos.json | 2 +- scm-ui/public/locales/en/users.json | 10 +++++----- 10 files changed, 48 insertions(+), 48 deletions(-) diff --git a/scm-ui/public/locales/de/commons.json b/scm-ui/public/locales/de/commons.json index 4a4b7c89cf..19dbfce733 100644 --- a/scm-ui/public/locales/de/commons.json +++ b/scm-ui/public/locales/de/commons.json @@ -1,7 +1,7 @@ { "login": { "title": "Anmeldung", - "subtitle": "Bitte anmelden um fortzufahren.", + "subtitle": "Bitte anmelden, um fortzufahren.", "logo-alt": "SCM-Manager", "username-placeholder": "Benutzername", "password-placeholder": "Passwort", @@ -59,10 +59,10 @@ "password": { "label": "Passwort", "newPassword": "Neues Passwort", - "passwordHelpText": "Plaintext Passwort des Benutzers.", - "passwordConfirmHelpText": "Passwort zur Bestätigen wiederholen.", + "passwordHelpText": "Klartext Passwort des Benutzers", + "passwordConfirmHelpText": "Passwort zur Bestätigen wiederholen", "currentPassword": "Aktuelles Passwort", - "currentPasswordHelpText": "Dieses Passwort wird momentan bereits verwendet.", + "currentPasswordHelpText": "Dieses Passwort wird momentan bereits verwendet", "confirmPassword": "Passwort wiederholen", "passwordInvalid": "Das Passwort muss zwischen 6 und 32 Zeichen lang sein", "passwordConfirmFailed": "Passwörter müssen identisch sein", diff --git a/scm-ui/public/locales/de/config.json b/scm-ui/public/locales/de/config.json index 0d8390a9bd..8a1f9035b1 100644 --- a/scm-ui/public/locales/de/config.json +++ b/scm-ui/public/locales/de/config.json @@ -47,12 +47,12 @@ "login-attempt": { "name": "Anmeldeversuche", "login-attempt-limit": "Limit für Anmeldeversuche", - "login-attempt-limit-timeout": "Timeout bei fehlgeschlagenen Anmeldeversuche" + "login-attempt-limit-timeout": "Timeout bei fehlgeschlagenen Anmeldeversuchen" }, "general-settings": { "realm-description": "Realm Beschreibung", "enable-repository-archive": "Repository Archiv aktivieren", - "disable-grouping-grid": "Gruppen deaktiviern", + "disable-grouping-grid": "Gruppen deaktivieren", "date-format": "Datumsformat", "anonymous-access-enabled": "Anonyme Zugriffe erlauben", "skip-failed-authenticators": "Fehlgeschlagene Authentifizierer überspringen", @@ -67,25 +67,25 @@ "plugin-url-invalid": "Dies ist keine gültige URL" }, "help": { - "realmDescriptionHelpText": "Beschreibung des authentication realm", - "dateFormatHelpText": "Moments Datumsformat. Zulässige Formate sind in der momentjs Dokumentation beschrieben.", + "realmDescriptionHelpText": "Beschreibung des Authentication Realm", + "dateFormatHelpText": "Moments Datumsformat. Zulässige Formate sind in der MomentJS Dokumentation beschrieben.", "pluginRepositoryHelpText": "Die URL des Plugin Repositories. Beschreibung der Platzhalter: version = SCM-Manager Version; os = Betriebssystem; arch = Architektur", "enableForwardingHelpText": "mod_proxy Port Weiterleitung aktivieren.", "enableRepositoryArchiveHelpText": "Repository Archive aktivieren. Nach einer Änderung an dieser Einstellung muss die Seite komplett neu geladen werden.", "disableGroupingGridHelpText": "Repository Gruppen deaktivieren. Nach einer Änderung an dieser Einstellung muss die Seite komplett neu geladen werden.", "allowAnonymousAccessHelpText": "Anonyme Benutzer haben Zugriff auf öffentliche Repositories.", - "skipFailedAuthenticatorsHelpText": "Die Kette der Authentifikatoren wird nicht beendet wenn ein Authentifikator einen Benutzer findet, ihn aber nicht erfolgreich authentifizieren kann.", + "skipFailedAuthenticatorsHelpText": "Die Kette der Authentifikatoren wird nicht beendet, wenn ein Authentifikator einen Benutzer findet, ihn aber nicht erfolgreich authentifizieren kann.", "adminGroupsHelpText": "Namen von Gruppen mit Admin-Berechtigungen.", "adminUsersHelpText": "Namen von Benutzern mit Admin-Berechtigungen.", "forceBaseUrlHelpText": "Zugriffe, die von einer anderen URL kommen, werden auf die Base URL weiter geleitet.", "baseUrlHelpText": "Die URL der Applikation mit Kontextpfad, z.B. http://localhost:8080/scm", "loginAttemptLimitHelpText": "Maximale Anzahl von Anmeldeversuchen. Durch Verwendung von -1 wird die Begrenzung der Anmeldeversuche deaktiviert.", - "loginAttemptLimitTimeoutHelpText": "Timeout in Sekunden für Benutzer, die vorübergehend wegen zu vieler fehlgeschlagener Anmeldeversuche deaktiviert wurden.", - "enableProxyHelpText": "Proxy aktiviern", + "loginAttemptLimitTimeoutHelpText": "Timeout in Sekunden für Benutzer, die vorübergehend wegen zu vieler fehlgeschlagener Anmeldeversuche, deaktiviert wurden.", + "enableProxyHelpText": "Proxy aktivieren", "proxyPortHelpText": "Der Proxy Port", - "proxyPasswordHelpText": "Das Passwort für die Proxy Server Anmeldung.", + "proxyPasswordHelpText": "Das Passwort für die Proxy Server Anmeldung", "proxyServerHelpText": "Der Proxy Server", - "proxyUserHelpText": "Der Benutzername für die Proxy Server Anmeldung.", + "proxyUserHelpText": "Der Benutzername für die Proxy Server Anmeldung", "proxyExcludesHelpText": "Glob patterns für Hostnamen, die von den Proxy-Einstellungen ausgeschlossen werden sollen.", "enableXsrfProtectionHelpText": "Xsrf Cookie Protection aktivieren. Hinweis: Dieses Feature befindet sich noch im Experimentalstatus.", "defaultNameSpaceStrategyHelpText": "Die Standardstrategie für Namespaces" diff --git a/scm-ui/public/locales/de/groups.json b/scm-ui/public/locales/de/groups.json index 86528ee82a..cb88997bf0 100644 --- a/scm-ui/public/locales/de/groups.json +++ b/scm-ui/public/locales/de/groups.json @@ -20,11 +20,11 @@ "back-label": "Zurück" }, "add-group": { - "title": "Gruppe erstellen", - "subtitle": "Erstllen einer neuen Gruppe" + "title": "Gruppe Erstellen", + "subtitle": "Erstellen einer neuen Gruppe" }, "create-group-button": { - "label": "Erstellen" + "label": "Gruppe Erstellen" }, "edit-group-button": { "label": "Bearbeiten" @@ -41,7 +41,7 @@ }, "add-member-autocomplete": { "placeholder": "Benutzername eingeben", - "loading": "suche...", + "loading": "Suche...", "no-options": "Kein Vorschlag für Benutzername verfügbar" }, @@ -50,7 +50,7 @@ "name-error": "Name ist ungültig", "description-error": "Beschreibung ist ungültig", "help": { - "nameHelpText": "Einzigartiger Name der Gruppe", + "nameHelpText": "Eindeutiger Name der Gruppe", "descriptionHelpText": "Eine kurze Beschreibung der Gruppe", "memberHelpText": "Benutzername des Mitglieds der Gruppe" } diff --git a/scm-ui/public/locales/de/repos.json b/scm-ui/public/locales/de/repos.json index 038d5cdc54..bdaef7e4b4 100644 --- a/scm-ui/public/locales/de/repos.json +++ b/scm-ui/public/locales/de/repos.json @@ -8,7 +8,7 @@ "lastModified": "Zuletzt bearbeitet" }, "validation": { - "name-invalid": "Der Name des Repositories ist ungültig", + "name-invalid": "Der Name des Repository ist ungültig", "contact-invalid": "Der Kontakt muss eine gültige E-Mail Adresse sein" }, "overview": { @@ -40,7 +40,7 @@ "delete-nav-action": { "label": "Löschen", "confirm-alert": { - "title": "Repository löschen", + "title": "Repository Löschen", "message": "Soll das Repository wirklich gelöscht werden?", "submit": "Ja", "cancel": "Nein" @@ -98,7 +98,7 @@ "user-permission": "Benutzerberechtigung", "edit-permission": { "delete-button": "Löschen", - "save-button": "Änderungen speichern" + "save-button": "Änderungen Speichern" }, "advanced-button": { "label": "Erweitert" @@ -106,7 +106,7 @@ "delete-permission-button": { "label": "Löschen", "confirm-alert": { - "title": "Berechtigung löschen", + "title": "Berechtigung Löschen", "message": "Soll die Berechtigung wirklich gelöscht werden?", "submit": "Ja", "cancel": "Nein" diff --git a/scm-ui/public/locales/de/users.json b/scm-ui/public/locales/de/users.json index bb033e02dd..56d438f723 100644 --- a/scm-ui/public/locales/de/users.json +++ b/scm-ui/public/locales/de/users.json @@ -7,7 +7,7 @@ "admin": "Admin", "active": "Aktiv", "type": "Typ", - "creationDate": "Erstell", + "creationDate": "Erstellt", "lastModified": "Zuletzt bearbeitet" }, "users": { @@ -15,12 +15,12 @@ "subtitle": "Verwaltung der Benutzer" }, "create-user-button": { - "label": "Erstellen" + "label": "Benutzer Erstellen" }, "delete-user-button": { "label": "Löschen", "confirm-alert": { - "title": "Benutzer löschen", + "title": "Benutzer Löschen", "message": "Soll der Benutzer wirklich gelöscht werden?", "submit": "Ja", "cancel": "Nein" @@ -30,7 +30,7 @@ "label": "Bearbeiten" }, "set-password-button": { - "label": "Passwort ändern" + "label": "Passwort Ändern" }, "set-permissions-button": { "label": "Berechtigungen ändern" @@ -39,7 +39,7 @@ "submit": "Speichern" }, "add-user": { - "title": "Benutzer erstellen", + "title": "Benutzer Erstellen", "subtitle": "Erstellen eines neuen Benutzers" }, "single-user": { @@ -59,10 +59,10 @@ "set-password-successful": "Das Passwort wurde erfolgreich gespeichert." }, "help": { - "usernameHelpText": "Einzigartiger Name des Benutzers.", - "displayNameHelpText": "Anzeigename des Benutzers.", - "mailHelpText": "E-Mail Adresse des Benutzers.", + "usernameHelpText": "Einzigartiger Name des Benutzers", + "displayNameHelpText": "Anzeigename des Benutzers", + "mailHelpText": "E-Mail Adresse des Benutzers", "adminHelpText": "Ein Administrator kann Repositories, Gruppen und Benutzer erstellen, bearbeiten und löschen.", - "activeHelpText": "Aktivierung oder Deaktivierung eines Benutzers." + "activeHelpText": "Aktivierung oder Deaktivierung eines Benutzers" } } diff --git a/scm-ui/public/locales/en/commons.json b/scm-ui/public/locales/en/commons.json index 6c3fa6628a..8d4f761a16 100644 --- a/scm-ui/public/locales/en/commons.json +++ b/scm-ui/public/locales/en/commons.json @@ -1,7 +1,7 @@ { "login": { "title": "Login", - "subtitle": "Please login to proceed.", + "subtitle": "Please login to proceed", "logo-alt": "SCM-Manager", "username-placeholder": "Your Username", "password-placeholder": "Your Password", @@ -22,7 +22,7 @@ "error-notification": { "prefix": "Error", "loginLink": "You can login here again.", - "timeout": "The session has expired.", + "timeout": "The session has expired", "wrong-login-credentials": "Invalid credentials" }, "loading": { @@ -59,8 +59,8 @@ "password": { "label": "Password", "newPassword": "New password", - "passwordHelpText": "Plain text password of the user.", - "passwordConfirmHelpText": "Repeat the password for confirmation.", + "passwordHelpText": "Plain text password of the user", + "passwordConfirmHelpText": "Repeat the password for confirmation", "currentPassword": "Current password", "currentPasswordHelpText": "The password currently in use", "confirmPassword": "Confirm password", diff --git a/scm-ui/public/locales/en/config.json b/scm-ui/public/locales/en/config.json index 1a33da8c8b..bc4848c353 100644 --- a/scm-ui/public/locales/en/config.json +++ b/scm-ui/public/locales/en/config.json @@ -68,26 +68,26 @@ }, "help": { "realmDescriptionHelpText": "Enter authentication realm description", - "dateFormatHelpText": "Moments date format. Please have a look at the momentjs documentation.", + "dateFormatHelpText": "Moments date format. Please have a look at the MomentJS documentation", "pluginRepositoryHelpText": "The url of the plugin repository. Explanation of the placeholders: version = SCM-Manager Version; os = Operation System; arch = Architecture", - "enableForwardingHelpText": "Enbale mod_proxy port forwarding.", + "enableForwardingHelpText": "Enable mod_proxy port forwarding", "enableRepositoryArchiveHelpText": "Enable repository archives. A complete page reload is required after a change of this value.", "disableGroupingGridHelpText": "Disable repository Groups. A complete page reload is required after a change of this value.", "allowAnonymousAccessHelpText": "Anonymous users have read access on public repositories.", "skipFailedAuthenticatorsHelpText": "Do not stop the authentication chain, if an authenticator finds the user but fails to authenticate the user.", - "adminGroupsHelpText": "Names of groups with admin permissions.", - "adminUsersHelpText": "Names of users with admin permissions.", + "adminGroupsHelpText": "Names of groups with admin permissions", + "adminUsersHelpText": "Names of users with admin permissions", "forceBaseUrlHelpText": "Redirects to the base url if the request comes from a other url", "baseUrlHelpText": "The url of the application (with context path), i.e. http://localhost:8080/scm", "loginAttemptLimitHelpText": "Maximum allowed login attempts. Use -1 to disable the login attempt limit.", "loginAttemptLimitTimeoutHelpText": "Timeout in seconds for users which are temporary disabled, because of too many failed login attempts.", "enableProxyHelpText": "Enable Proxy", "proxyPortHelpText": "The proxy port", - "proxyPasswordHelpText": "The password for the proxy server authentication.", + "proxyPasswordHelpText": "The password for the proxy server authentication", "proxyServerHelpText": "The proxy server", "proxyUserHelpText": "The username for the proxy server authentication.", "proxyExcludesHelpText": "Glob patterns for hostnames which should be excluded from proxy settings.", - "enableXsrfProtectionHelpText": "Enable Xsrf Cookie Protection. Note: This feature is still experimental.", + "enableXsrfProtectionHelpText": "Enable XSRF Cookie Protection. Note: This feature is still experimental.", "defaultNameSpaceStrategyHelpText": "The default namespace strategy" } } diff --git a/scm-ui/public/locales/en/groups.json b/scm-ui/public/locales/en/groups.json index 3fbe088029..0eb5e74cfc 100644 --- a/scm-ui/public/locales/en/groups.json +++ b/scm-ui/public/locales/en/groups.json @@ -24,7 +24,7 @@ "subtitle": "Create a new group" }, "create-group-button": { - "label": "Create" + "label": "Create Group" }, "edit-group-button": { "label": "Edit" diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 7eef6f79b1..ea7b8fd118 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -14,7 +14,7 @@ "overview": { "title": "Repositories", "subtitle": "Overview of available repositories", - "create-button": "Create" + "create-button": "Create Repository" }, "repository-root": { "error-title": "Error", diff --git a/scm-ui/public/locales/en/users.json b/scm-ui/public/locales/en/users.json index afe86deb9b..04b3a96274 100644 --- a/scm-ui/public/locales/en/users.json +++ b/scm-ui/public/locales/en/users.json @@ -15,7 +15,7 @@ "subtitle": "Create, read, update and delete users" }, "create-user-button": { - "label": "Create" + "label": "Create User" }, "delete-user-button": { "label": "Delete", @@ -59,10 +59,10 @@ "set-password-successful": "Password successfully set" }, "help": { - "usernameHelpText": "Unique name of the user.", - "displayNameHelpText": "Display name of the user.", - "mailHelpText": "Email address of the user.", + "usernameHelpText": "Unique name of the user", + "displayNameHelpText": "Display name of the user", + "mailHelpText": "Email address of the user", "adminHelpText": "An administrator is able to create, modify and delete repositories, groups and users.", - "activeHelpText": "Activate or deactive the user." + "activeHelpText": "Activate or deactivate the user" } } From 7962463be25ad0d5526cea7b6229326a124fd635 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Thu, 31 Jan 2019 11:47:10 +0100 Subject: [PATCH 051/112] update hibernate-validator to version 5.3.6.Final, to fix CVE-2017-7536 --- scm-webapp/pom.xml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index dfaac3b4c0..ccfc312999 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -152,11 +152,22 @@ <version>${resteasy.version}</version> </dependency> + <dependency> + <groupId>org.hibernate</groupId> + <artifactId>hibernate-validator</artifactId> + <version>5.3.6.Final</version> + </dependency> + <dependency> <groupId>javax.el</groupId> <artifactId>javax.el-api</artifactId> - <version>3.0.1-b06</version> - <scope>provided</scope> + <version>2.2.4</version> + </dependency> + + <dependency> + <groupId>org.glassfish.web</groupId> + <artifactId>javax.el</artifactId> + <version>2.2.4</version> </dependency> <!-- injection --> From c388bb761e31eace4c5b497a54c455df4c4e225e Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Thu, 31 Jan 2019 11:53:16 +0100 Subject: [PATCH 052/112] remove duplicated declaration jackson.version --- scm-webapp/pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index ccfc312999..06971f713d 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -573,7 +573,6 @@ <selenium.version>2.53.1</selenium.version> <wagon.version>1.0</wagon.version> <mustache.version>0.8.17</mustache.version> - <jackson.version>2.8.9</jackson.version> <netbeans.hint.deploy.server>Tomcat</netbeans.hint.deploy.server> <sonar.issue.ignore.multicriteria>e1</sonar.issue.ignore.multicriteria> <sonar.issue.ignore.multicriteria.e1.ruleKey>javascript:S3827</sonar.issue.ignore.multicriteria.e1.ruleKey> From bd91036f725ee0bbe25da89d4e69209c77db47fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= <rene.pfeuffer@cloudogu.com> Date: Thu, 31 Jan 2019 11:56:27 +0100 Subject: [PATCH 053/112] Add unit test --- .../api/v2/resources/ConfigResourceTest.java | 42 ++++++++++++++----- .../api/v2/config-test-empty-admin-group.json | 3 ++ .../api/v2/config-test-empty-admin-user.json | 3 ++ 3 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-empty-admin-group.json create mode 100644 scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-empty-admin-user.json diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java index 033824cbea..4fb25d2371 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java @@ -92,11 +92,7 @@ public class ConfigResourceTest { @Test @SubjectAware(username = "readWrite") public void shouldUpdateConfig() throws URISyntaxException, IOException { - URL url = Resources.getResource("sonia/scm/api/v2/config-test-update.json"); - byte[] configJson = Resources.toByteArray(url); - MockHttpRequest request = MockHttpRequest.put("/" + ConfigResource.CONFIG_PATH_V2) - .contentType(VndMediaType.CONFIG) - .content(configJson); + MockHttpRequest request = post("sonia/scm/api/v2/config-test-update.json"); MockHttpResponse response = new MockHttpResponse(); dispatcher.invoke(request, response); @@ -113,11 +109,7 @@ public class ConfigResourceTest { @Test @SubjectAware(username = "readOnly") public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException, IOException { - URL url = Resources.getResource("sonia/scm/api/v2/config-test-update.json"); - byte[] configJson = Resources.toByteArray(url); - MockHttpRequest request = MockHttpRequest.put("/" + ConfigResource.CONFIG_PATH_V2) - .contentType(VndMediaType.CONFIG) - .content(configJson); + MockHttpRequest request = post("sonia/scm/api/v2/config-test-update.json"); MockHttpResponse response = new MockHttpResponse(); thrown.expectMessage("Subject does not have permission [configuration:write:global]"); @@ -125,6 +117,36 @@ public class ConfigResourceTest { dispatcher.invoke(request, response); } + @Test + @SubjectAware(username = "readWrite") + public void shouldFailForEmptyAdminUsers() throws URISyntaxException, IOException { + MockHttpRequest request = post("sonia/scm/api/v2/config-test-empty-admin-user.json"); + + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + + assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus()); + } + + @Test + @SubjectAware(username = "readWrite") + public void shouldFailForEmptyAdminGroups() throws URISyntaxException, IOException { + MockHttpRequest request = post("sonia/scm/api/v2/config-test-empty-admin-group.json"); + + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + + assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus()); + } + + private MockHttpRequest post(String resourceName) throws IOException, URISyntaxException { + URL url = Resources.getResource(resourceName); + byte[] configJson = Resources.toByteArray(url); + return MockHttpRequest.put("/" + ConfigResource.CONFIG_PATH_V2) + .contentType(VndMediaType.CONFIG) + .content(configJson); + } + private static ScmConfiguration createConfiguration() { ScmConfiguration scmConfiguration = new ScmConfiguration(); scmConfiguration.setProxyPassword("heartOfGold"); diff --git a/scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-empty-admin-group.json b/scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-empty-admin-group.json new file mode 100644 index 0000000000..f665c29ee7 --- /dev/null +++ b/scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-empty-admin-group.json @@ -0,0 +1,3 @@ +{ + "adminGroups": [""] +} diff --git a/scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-empty-admin-user.json b/scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-empty-admin-user.json new file mode 100644 index 0000000000..61efcb1609 --- /dev/null +++ b/scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-empty-admin-user.json @@ -0,0 +1,3 @@ +{ + "adminUsers": [""] +} From ed57450dc3971b3f22476ced3ebdcb4736bbd043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= <rene.pfeuffer@cloudogu.com> Date: Thu, 31 Jan 2019 12:00:40 +0100 Subject: [PATCH 054/112] Add unit test --- .../NoBlankStringsValidatorTest.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 scm-webapp/src/test/java/sonia/scm/api/v2/resources/NoBlankStringsValidatorTest.java diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/NoBlankStringsValidatorTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/NoBlankStringsValidatorTest.java new file mode 100644 index 0000000000..1929b0bb06 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/NoBlankStringsValidatorTest.java @@ -0,0 +1,28 @@ +package sonia.scm.api.v2.resources; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; + +import static java.util.Collections.emptySet; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class NoBlankStringsValidatorTest { + + @Test + void shouldAcceptNonEmptyElements() { + assertTrue(new NoBlankStringsValidator().isValid(Arrays.asList("not", "empty"), null)); + } + + @Test + void shouldFailForEmptyElements() { + assertFalse(new NoBlankStringsValidator().isValid(Arrays.asList("one", "", "three"), null)); + } + + @Test + void shouldAcceptEmptyList() { + assertTrue(new NoBlankStringsValidator().isValid(emptySet(), null)); + } +} From 6ff8e410f243bfe2dc22676e1583a290d1aefa9a Mon Sep 17 00:00:00 2001 From: Iwan Schindler <iwan.schindler@cloudogu.com> Date: Thu, 31 Jan 2019 14:22:15 +0100 Subject: [PATCH 055/112] de/en language modifications --- scm-ui/public/locales/de/commons.json | 18 +++++++++--------- scm-ui/public/locales/de/config.json | 8 ++++---- scm-ui/public/locales/de/groups.json | 4 ++-- scm-ui/public/locales/de/repos.json | 18 +++++++++--------- scm-ui/public/locales/de/users.json | 8 ++++---- scm-ui/public/locales/en/commons.json | 2 +- scm-ui/public/locales/en/config.json | 18 +++++++++--------- scm-ui/public/locales/en/groups.json | 10 +++++----- scm-ui/public/locales/en/permissions.json | 2 +- scm-ui/public/locales/en/repos.json | 10 +++++----- scm-ui/public/locales/en/users.json | 14 +++++++------- 11 files changed, 56 insertions(+), 56 deletions(-) diff --git a/scm-ui/public/locales/de/commons.json b/scm-ui/public/locales/de/commons.json index 19dbfce733..c38dff2468 100644 --- a/scm-ui/public/locales/de/commons.json +++ b/scm-ui/public/locales/de/commons.json @@ -10,13 +10,13 @@ "logout": { "error": { "title": "Abmeldung fehlgeschlagen", - "subtitle": "Während der Abmeldung ist ein Fehler aufgetreten" + "subtitle": "Während der Abmeldung ist ein Fehler aufgetreten." } }, "app": { "error": { "title": "Fehler", - "subtitle": "Ein unbekannter Fehler ist aufgetreten" + "subtitle": "Ein unbekannter Fehler ist aufgetreten." } }, "error-notification": { @@ -52,21 +52,21 @@ "information": "Informationen", "change-password": "Passwort ändern", "error-title": "Fehler", - "error-subtitle": "Das Profil kann nicht angezeigt werden", + "error-subtitle": "Das Profil kann nicht angezeigt werden.", "error": "Fehler", "error-message": "'me' ist nicht definiert" }, "password": { "label": "Passwort", "newPassword": "Neues Passwort", - "passwordHelpText": "Klartext Passwort des Benutzers", - "passwordConfirmHelpText": "Passwort zur Bestätigen wiederholen", + "passwordHelpText": "Klartext Passwort des Benutzers.", + "passwordConfirmHelpText": "Passwort zur Bestätigen wiederholen.", "currentPassword": "Aktuelles Passwort", - "currentPasswordHelpText": "Dieses Passwort wird momentan bereits verwendet", + "currentPasswordHelpText": "Dieses Passwort wird momentan bereits verwendet.", "confirmPassword": "Passwort wiederholen", - "passwordInvalid": "Das Passwort muss zwischen 6 und 32 Zeichen lang sein", - "passwordConfirmFailed": "Passwörter müssen identisch sein", + "passwordInvalid": "Das Passwort muss zwischen 6 und 32 Zeichen lang sein!", + "passwordConfirmFailed": "Passwörter müssen identisch sein!", "submit": "Speichern", - "changedSuccessfully": "Passwort erfolgreich geändert" + "changedSuccessfully": "Passwort erfolgreich geändert!" } } diff --git a/scm-ui/public/locales/de/config.json b/scm-ui/public/locales/de/config.json index 8a1f9035b1..e26c2cddac 100644 --- a/scm-ui/public/locales/de/config.json +++ b/scm-ui/public/locales/de/config.json @@ -67,7 +67,7 @@ "plugin-url-invalid": "Dies ist keine gültige URL" }, "help": { - "realmDescriptionHelpText": "Beschreibung des Authentication Realm", + "realmDescriptionHelpText": "Beschreibung des Authentication Realm.", "dateFormatHelpText": "Moments Datumsformat. Zulässige Formate sind in der MomentJS Dokumentation beschrieben.", "pluginRepositoryHelpText": "Die URL des Plugin Repositories. Beschreibung der Platzhalter: version = SCM-Manager Version; os = Betriebssystem; arch = Architektur", "enableForwardingHelpText": "mod_proxy Port Weiterleitung aktivieren.", @@ -83,11 +83,11 @@ "loginAttemptLimitTimeoutHelpText": "Timeout in Sekunden für Benutzer, die vorübergehend wegen zu vieler fehlgeschlagener Anmeldeversuche, deaktiviert wurden.", "enableProxyHelpText": "Proxy aktivieren", "proxyPortHelpText": "Der Proxy Port", - "proxyPasswordHelpText": "Das Passwort für die Proxy Server Anmeldung", + "proxyPasswordHelpText": "Das Passwort für die Proxy Server Anmeldung.", "proxyServerHelpText": "Der Proxy Server", - "proxyUserHelpText": "Der Benutzername für die Proxy Server Anmeldung", + "proxyUserHelpText": "Der Benutzername für die Proxy Server Anmeldung.", "proxyExcludesHelpText": "Glob patterns für Hostnamen, die von den Proxy-Einstellungen ausgeschlossen werden sollen.", "enableXsrfProtectionHelpText": "Xsrf Cookie Protection aktivieren. Hinweis: Dieses Feature befindet sich noch im Experimentalstatus.", - "defaultNameSpaceStrategyHelpText": "Die Standardstrategie für Namespaces" + "defaultNameSpaceStrategyHelpText": "Die Standardstrategie für Namespaces." } } diff --git a/scm-ui/public/locales/de/groups.json b/scm-ui/public/locales/de/groups.json index cb88997bf0..768704bf38 100644 --- a/scm-ui/public/locales/de/groups.json +++ b/scm-ui/public/locales/de/groups.json @@ -20,11 +20,11 @@ "back-label": "Zurück" }, "add-group": { - "title": "Gruppe Erstellen", + "title": "Gruppe erstellen", "subtitle": "Erstellen einer neuen Gruppe" }, "create-group-button": { - "label": "Gruppe Erstellen" + "label": "Gruppe erstellen" }, "edit-group-button": { "label": "Bearbeiten" diff --git a/scm-ui/public/locales/de/repos.json b/scm-ui/public/locales/de/repos.json index bdaef7e4b4..e82edf7512 100644 --- a/scm-ui/public/locales/de/repos.json +++ b/scm-ui/public/locales/de/repos.json @@ -14,7 +14,7 @@ "overview": { "title": "Repositories", "subtitle": "Übersicht aller verfügbaren Repositories", - "create-button": "Erstellen" + "create-button": "Repository erstellen" }, "repository-root": { "error-title": "Fehler", @@ -28,8 +28,8 @@ "sources": "Sources" }, "create": { - "title": "Repository Erstellen", - "subtitle": "Erstellen eines neuen Repositories" + "title": "Repository erstellen", + "subtitle": "Erstellen eines neuen Repository" }, "repository-form": { "submit": "Speichern" @@ -40,7 +40,7 @@ "delete-nav-action": { "label": "Löschen", "confirm-alert": { - "title": "Repository Löschen", + "title": "Repository löschen", "message": "Soll das Repository wirklich gelöscht werden?", "submit": "Ja", "cancel": "Nein" @@ -106,7 +106,7 @@ "delete-permission-button": { "label": "Löschen", "confirm-alert": { - "title": "Berechtigung Löschen", + "title": "Berechtigung löschen", "message": "Soll die Berechtigung wirklich gelöscht werden?", "submit": "Ja", "cancel": "Nein" @@ -120,7 +120,7 @@ "help": { "groupPermissionHelpText": "Zeigt ob es sich bei der Berechtigung um eine Gruppenberechtigung handelt. Wenn hier kein Haken gesetzt ist, handelt es sich um eine Benutzerberechtigung.", "nameHelpText": "Verwaltung von Berechtigungen für Benutzer und Gruppen", - "roleHelpText": "READ = read; WRITE = read und write; OWNER = read, write und auch die Möglichkeit Einstellungen und Berechtigungen zu verwalten. Wenn hier nichts angezeigt wird den Erweitert-Button benutzen um Details zu sehen.", + "roleHelpText": "READ = read; WRITE = read und write; OWNER = read, write und auch die Möglichkeit Einstellungen und Berechtigungen zu verwalten. Wenn hier nichts angezeigt wird, den Erweitert-Button benutzen, um Details zu sehen.", "permissionsHelpText": "Hier können individuelle Berechtigungen unabhängig von vordefinierten Rollen vergeben werden." }, "autocomplete": { @@ -139,9 +139,9 @@ } }, "help": { - "nameHelpText": "Der Name des Repositories. Dieser wird Teil der URL des Repositories sein.", - "typeHelpText": "Der Typ des Repositories (Mercurial, Git oder Subversion).", + "nameHelpText": "Der Name des Repository. Dieser wird Teil der URL des Repository sein.", + "typeHelpText": "Der Typ des Repository (Mercurial, Git oder Subversion).", "contactHelpText": "E-Mail Adresse der Person, die für das Repository verantwortlich ist.", - "descriptionHelpText": "Eine kurze Beschreibung des Repositories." + "descriptionHelpText": "Eine kurze Beschreibung des Repository." } } diff --git a/scm-ui/public/locales/de/users.json b/scm-ui/public/locales/de/users.json index 56d438f723..31b954d996 100644 --- a/scm-ui/public/locales/de/users.json +++ b/scm-ui/public/locales/de/users.json @@ -15,12 +15,12 @@ "subtitle": "Verwaltung der Benutzer" }, "create-user-button": { - "label": "Benutzer Erstellen" + "label": "Benutzer erstellen" }, "delete-user-button": { "label": "Löschen", "confirm-alert": { - "title": "Benutzer Löschen", + "title": "Benutzer löschen", "message": "Soll der Benutzer wirklich gelöscht werden?", "submit": "Ja", "cancel": "Nein" @@ -30,7 +30,7 @@ "label": "Bearbeiten" }, "set-password-button": { - "label": "Passwort Ändern" + "label": "Passwort ändern" }, "set-permissions-button": { "label": "Berechtigungen ändern" @@ -39,7 +39,7 @@ "submit": "Speichern" }, "add-user": { - "title": "Benutzer Erstellen", + "title": "Benutzer erstellen", "subtitle": "Erstellen eines neuen Benutzers" }, "single-user": { diff --git a/scm-ui/public/locales/en/commons.json b/scm-ui/public/locales/en/commons.json index 8d4f761a16..4788f7b39f 100644 --- a/scm-ui/public/locales/en/commons.json +++ b/scm-ui/public/locales/en/commons.json @@ -50,7 +50,7 @@ "mail": "E-Mail", "groups": "Groups", "information": "Information", - "change-password": "Change password", + "change-password": "Change Password", "error-title": "Error", "error-subtitle": "Cannot display profile", "error": "Error", diff --git a/scm-ui/public/locales/en/config.json b/scm-ui/public/locales/en/config.json index bc4848c353..bdc7ec21f6 100644 --- a/scm-ui/public/locales/en/config.json +++ b/scm-ui/public/locales/en/config.json @@ -67,27 +67,27 @@ "plugin-url-invalid": "This is not a valid url" }, "help": { - "realmDescriptionHelpText": "Enter authentication realm description", - "dateFormatHelpText": "Moments date format. Please have a look at the MomentJS documentation", + "realmDescriptionHelpText": "Enter authentication realm description.", + "dateFormatHelpText": "Moments date format. Please have a look at the MomentJS documentation.", "pluginRepositoryHelpText": "The url of the plugin repository. Explanation of the placeholders: version = SCM-Manager Version; os = Operation System; arch = Architecture", - "enableForwardingHelpText": "Enable mod_proxy port forwarding", + "enableForwardingHelpText": "Enable mod_proxy port forwarding.", "enableRepositoryArchiveHelpText": "Enable repository archives. A complete page reload is required after a change of this value.", "disableGroupingGridHelpText": "Disable repository Groups. A complete page reload is required after a change of this value.", "allowAnonymousAccessHelpText": "Anonymous users have read access on public repositories.", "skipFailedAuthenticatorsHelpText": "Do not stop the authentication chain, if an authenticator finds the user but fails to authenticate the user.", - "adminGroupsHelpText": "Names of groups with admin permissions", - "adminUsersHelpText": "Names of users with admin permissions", - "forceBaseUrlHelpText": "Redirects to the base url if the request comes from a other url", + "adminGroupsHelpText": "Names of groups with admin permissions.", + "adminUsersHelpText": "Names of users with admin permissions.", + "forceBaseUrlHelpText": "Redirects to the base url if the request comes from a other url.", "baseUrlHelpText": "The url of the application (with context path), i.e. http://localhost:8080/scm", "loginAttemptLimitHelpText": "Maximum allowed login attempts. Use -1 to disable the login attempt limit.", "loginAttemptLimitTimeoutHelpText": "Timeout in seconds for users which are temporary disabled, because of too many failed login attempts.", "enableProxyHelpText": "Enable Proxy", "proxyPortHelpText": "The proxy port", - "proxyPasswordHelpText": "The password for the proxy server authentication", + "proxyPasswordHelpText": "The password for the proxy server authentication.", "proxyServerHelpText": "The proxy server", "proxyUserHelpText": "The username for the proxy server authentication.", - "proxyExcludesHelpText": "Glob patterns for hostnames which should be excluded from proxy settings.", + "proxyExcludesHelpText": "Glob patterns for hostnames, which should be excluded from proxy settings.", "enableXsrfProtectionHelpText": "Enable XSRF Cookie Protection. Note: This feature is still experimental.", - "defaultNameSpaceStrategyHelpText": "The default namespace strategy" + "defaultNameSpaceStrategyHelpText": "The default namespace strategy." } } diff --git a/scm-ui/public/locales/en/groups.json b/scm-ui/public/locales/en/groups.json index 0eb5e74cfc..60a10e4302 100644 --- a/scm-ui/public/locales/en/groups.json +++ b/scm-ui/public/locales/en/groups.json @@ -30,17 +30,17 @@ "label": "Edit" }, "add-member-button": { - "label": "Add member" + "label": "Add Member" }, "remove-member-button": { - "label": "Remove member" + "label": "Remove Member" }, "add-member-textfield": { - "label": "Add member", + "label": "Add Member", "error": "Invalid member name" }, "add-member-autocomplete": { - "placeholder": "Enter member", + "placeholder": "Enter Member", "loading": "Loading...", "no-options": "No suggestion available" }, @@ -65,6 +65,6 @@ } }, "set-permissions-button": { - "label": "Set permissions" + "label": "Set Permissions" } } diff --git a/scm-ui/public/locales/en/permissions.json b/scm-ui/public/locales/en/permissions.json index 52059db60a..f5ba065ced 100644 --- a/scm-ui/public/locales/en/permissions.json +++ b/scm-ui/public/locales/en/permissions.json @@ -1,7 +1,7 @@ { "form": { "submit-button": { - "label": "Set permissions" + "label": "Set Permissions" }, "set-permissions-successful": "Permissions set successfully" } diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index ea7b8fd118..727fe4f664 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -40,7 +40,7 @@ "delete-nav-action": { "label": "Delete", "confirm-alert": { - "title": "Delete repository", + "title": "Delete Repository", "message": "Do you really want to delete the repository?", "submit": "Yes", "cancel": "No" @@ -106,7 +106,7 @@ "delete-permission-button": { "label": "Delete", "confirm-alert": { - "title": "Delete permission", + "title": "Delete Permission", "message": "Do you really want to delete the permission?", "submit": "Yes", "cancel": "No" @@ -119,9 +119,9 @@ }, "help": { "groupPermissionHelpText": "States if a permission is a group permission. If this is not checked, it is a user permission.", - "nameHelpText": "Manage permissions for a specific user or group", + "nameHelpText": "Manage permissions for a specific user or group.", "roleHelpText": "READ = read; WRITE = read and write; OWNER = read, write and also the ability to manage the properties and permissions. If nothing is selected here, use the 'Advanced' Button to see detailed permissions.", - "permissionsHelpText": "Use this to specify your own set of permissions regardless of predefined roles" + "permissionsHelpText": "Use this to specify your own set of permissions regardless of predefined roles." }, "autocomplete": { "no-group-options": "No group suggestion available", @@ -132,7 +132,7 @@ }, "advanced": { "dialog": { - "title": "Advanced permissions", + "title": "Advanced Permissions", "submit": "Submit", "abort": "Abort" } diff --git a/scm-ui/public/locales/en/users.json b/scm-ui/public/locales/en/users.json index 04b3a96274..4c671617ec 100644 --- a/scm-ui/public/locales/en/users.json +++ b/scm-ui/public/locales/en/users.json @@ -20,7 +20,7 @@ "delete-user-button": { "label": "Delete", "confirm-alert": { - "title": "Delete user", + "title": "Delete User", "message": "Do you really want to delete the user?", "submit": "Yes", "cancel": "No" @@ -30,10 +30,10 @@ "label": "Edit" }, "set-password-button": { - "label": "Set password" + "label": "Set Password" }, "set-permissions-button": { - "label": "Set permissions" + "label": "Set Permissions" }, "user-form": { "submit": "Submit" @@ -59,10 +59,10 @@ "set-password-successful": "Password successfully set" }, "help": { - "usernameHelpText": "Unique name of the user", - "displayNameHelpText": "Display name of the user", - "mailHelpText": "Email address of the user", + "usernameHelpText": "Unique name of the user.", + "displayNameHelpText": "Display name of the user.", + "mailHelpText": "Email address of the user.", "adminHelpText": "An administrator is able to create, modify and delete repositories, groups and users.", - "activeHelpText": "Activate or deactivate the user" + "activeHelpText": "Activate or deactivate the user." } } From 145ac7a8d80742a364290629b52c4f5cd7831953 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Thu, 31 Jan 2019 15:53:19 +0100 Subject: [PATCH 056/112] remove direct dependencies to apache beanutils and commons collections --- pom.xml | 15 ---- .../scm/repository/RepositoryPermission.java | 4 +- .../main/java/sonia/scm/util/Comparables.java | 88 ++++++++++++++++++ .../java/sonia/scm/util/ComparablesTest.java | 57 ++++++++++++ scm-webapp/pom.xml | 12 --- .../resources/AbstractManagerResource.java | 89 ++----------------- .../sonia/scm/security/RepositoryRole.java | 7 +- 7 files changed, 156 insertions(+), 116 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/util/Comparables.java create mode 100644 scm-core/src/test/java/sonia/scm/util/ComparablesTest.java diff --git a/pom.xml b/pom.xml index a40f09c441..a7108df873 100644 --- a/pom.xml +++ b/pom.xml @@ -351,21 +351,6 @@ <scope>test</scope> </dependency> - - <!-- utils --> - - <dependency> - <groupId>commons-beanutils</groupId> - <artifactId>commons-beanutils</artifactId> - <version>1.9.3</version> - </dependency> - - <dependency> - <groupId>commons-collections</groupId> - <artifactId>commons-collections</artifactId> - <version>3.2.2</version> - </dependency> - <!-- http --> <dependency> diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java index 9e132ef93c..5fd14f2e84 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java @@ -37,7 +37,6 @@ package sonia.scm.repository; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; -import org.apache.commons.collections.CollectionUtils; import sonia.scm.security.PermissionObject; import javax.xml.bind.annotation.XmlAccessType; @@ -109,7 +108,8 @@ public class RepositoryPermission implements PermissionObject, Serializable final RepositoryPermission other = (RepositoryPermission) obj; return Objects.equal(name, other.name) - && CollectionUtils.isEqualCollection(verbs, other.verbs) + && verbs.containsAll(other.verbs) + && verbs.size() == other.verbs.size() && Objects.equal(groupPermission, other.groupPermission); } diff --git a/scm-core/src/main/java/sonia/scm/util/Comparables.java b/scm-core/src/main/java/sonia/scm/util/Comparables.java new file mode 100644 index 0000000000..b760021c07 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/util/Comparables.java @@ -0,0 +1,88 @@ +package sonia.scm.util; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Optional; + +import static com.google.common.base.Preconditions.checkArgument; + +public final class Comparables { + + private static final CacheLoader<Class, BeanInfo> beanInfoCacheLoader = new CacheLoader<Class, BeanInfo>() { + @Override + public BeanInfo load(Class type) throws IntrospectionException { + return Introspector.getBeanInfo(type); + } + }; + + private static final LoadingCache<Class, BeanInfo> beanInfoCache = CacheBuilder.newBuilder() + .maximumSize(50) // limit the cache to avoid consuming to much memory on miss usage + .build(beanInfoCacheLoader); + + private Comparables() { + } + + public static <T> Comparator<T> comparator(Class<T> type, String sortBy) { + BeanInfo info = createBeanInfo(type); + PropertyDescriptor propertyDescriptor = findPropertyDescriptor(sortBy, info); + + Method readMethod = propertyDescriptor.getReadMethod(); + checkIfPropertyIsComparable(readMethod, sortBy); + + return new MethodComparator<>(readMethod); + } + + private static void checkIfPropertyIsComparable(Method readMethod, String sortBy) { + checkArgument(isReturnTypeComparable(readMethod), "property %s is not comparable", sortBy); + } + + private static boolean isReturnTypeComparable(Method readMethod) { + return Comparable.class.isAssignableFrom(readMethod.getReturnType()); + } + + private static PropertyDescriptor findPropertyDescriptor(String sortBy, BeanInfo info) { + PropertyDescriptor[] propertyDescriptors = info.getPropertyDescriptors(); + + Optional<PropertyDescriptor> optional = Arrays.stream(propertyDescriptors) + .filter(p -> p.getName().equals(sortBy)) + .findFirst(); + + return optional.orElseThrow(() -> new IllegalArgumentException("could not find property " + sortBy)); + } + + private static <T> BeanInfo createBeanInfo(Class<T> type) { + return beanInfoCache.getUnchecked(type); + } + + private static class MethodComparator<T> implements Comparator<T> { + + private final Method readMethod; + + private MethodComparator(Method readMethod) { + this.readMethod = readMethod; + } + + @Override + @SuppressWarnings("unchecked") + public int compare(T left, T right) { + try { + Comparable leftResult = (Comparable) readMethod.invoke(left); + Comparable rightResult = (Comparable) readMethod.invoke(right); + return leftResult.compareTo(rightResult); + } catch (IllegalAccessException | InvocationTargetException ex) { + throw new IllegalArgumentException("failed to invoke read method", ex); + } + } + } + +} diff --git a/scm-core/src/test/java/sonia/scm/util/ComparablesTest.java b/scm-core/src/test/java/sonia/scm/util/ComparablesTest.java new file mode 100644 index 0000000000..50ae2254e6 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/util/ComparablesTest.java @@ -0,0 +1,57 @@ +package sonia.scm.util; + +import org.junit.jupiter.api.Test; + +import java.util.Comparator; + +import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ComparablesTest { + + @Test + void shouldCompare() { + One a = new One("a"); + One b = new One("b"); + + Comparator<One> comparable = Comparables.comparator(One.class, "value"); + assertThat(comparable.compare(a, b)).isEqualTo(-1); + } + + @Test + void shouldThrowAnExceptionForNonExistingField() { + assertThrows(IllegalArgumentException.class, () -> Comparables.comparator(One.class, "awesome")); + } + + @Test + void shouldThrowAnExceptionForNonComparableField() { + assertThrows(IllegalArgumentException.class, () -> Comparables.comparator(One.class, "nonComparable")); + } + + @Test + void shouldThrowAnExceptionIfTheFieldHasNoGetter() { + assertThrows(IllegalArgumentException.class, () -> Comparables.comparator(One.class, "incredible")); + } + + private static class One { + + private String value; + private String incredible; + private NonComparable nonComparable; + + One(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public NonComparable getNonComparable() { + return nonComparable; + } + } + + private static class NonComparable {} + +} diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 06971f713d..9f7e311820 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -215,18 +215,6 @@ <version>1.4.01</version> </dependency> - <!-- only for BeanComparator, replace with own implementation --> - - <dependency> - <groupId>commons-beanutils</groupId> - <artifactId>commons-beanutils</artifactId> - </dependency> - - <dependency> - <groupId>commons-collections</groupId> - <artifactId>commons-collections</artifactId> - </dependency> - <!-- fix installation of httpasswd-plugin https://groups.google.com/d/topic/scmmanager/eN7UtG8TwW8/discussion diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AbstractManagerResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AbstractManagerResource.java index 4253c456fb..dfc0bd2a5d 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AbstractManagerResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AbstractManagerResource.java @@ -37,7 +37,6 @@ package sonia.scm.api.rest.resources; import com.google.common.annotations.VisibleForTesting; import com.google.common.net.UrlEscapers; -import org.apache.commons.beanutils.BeanComparator; import org.apache.shiro.authz.AuthorizationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,6 +46,7 @@ import sonia.scm.ModelObject; import sonia.scm.PageResult; import sonia.scm.api.rest.RestExceptionResult; import sonia.scm.util.AssertUtil; +import sonia.scm.util.Comparables; import sonia.scm.util.Util; import javax.ws.rs.core.CacheControl; @@ -56,15 +56,10 @@ import javax.ws.rs.core.Request; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; -import java.beans.BeanInfo; -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; -import java.util.Arrays; +import java.net.URI; import java.util.Collection; import java.util.Comparator; import java.util.Date; -import java.net.URI; //~--- JDK imports ------------------------------------------------------------ @@ -510,21 +505,11 @@ public abstract class AbstractManagerResource<T extends ModelObject> { return builder.build(); } - @SuppressWarnings("unchecked") - private Comparator<T> createComparator(String sortBy, boolean desc) - { - checkSortByField(sortBy); - Comparator comparator; - - if (desc) - { - comparator = new BeanReverseComparator(sortBy); + private Comparator<T> createComparator(String sortBy, boolean desc) { + Comparator<T> comparator = Comparables.comparator(type, sortBy); + if (desc) { + comparator = comparator.reversed(); } - else - { - comparator = new BeanComparator(sortBy); - } - return comparator; } @@ -558,21 +543,6 @@ public abstract class AbstractManagerResource<T extends ModelObject> { return items; } - // We have to handle IntrospectionException here, because it's a checked exception - // It shouldn't occur really - so creating a new unchecked exception would be over-engineered here - @SuppressWarnings("squid:S00112") - private void checkSortByField(String sortBy) { - try { - BeanInfo info = Introspector.getBeanInfo(type); - PropertyDescriptor[] pds = info.getPropertyDescriptors(); - if (Arrays.stream(pds).noneMatch(p -> p.getName().equals(sortBy))) { - throw new IllegalArgumentException("sortBy"); - } - } catch (IntrospectionException e) { - throw new RuntimeException("error introspecting model type " + type.getName(), e); - } - } - protected PageResult<T> fetchPage(String sortBy, boolean desc, int pageNumber, int pageSize) { AssertUtil.assertPositive(pageNumber); @@ -608,51 +578,4 @@ public abstract class AbstractManagerResource<T extends ModelObject> { return lastModified; } - - //~--- inner classes -------------------------------------------------------- - - /** - * Class description - * - * - * @version Enter version here..., 11/06/09 - * @author Enter your name here... - */ - private static class BeanReverseComparator extends BeanComparator - { - - /** Field description */ - private static final long serialVersionUID = -8535047820348790009L; - - //~--- constructors ------------------------------------------------------- - - /** - * Constructs ... - * - * - * @param sortby - */ - private BeanReverseComparator(String sortby) - { - super(sortby); - } - - //~--- methods ------------------------------------------------------------ - - /** - * Method description - * - * - * @param o1 - * @param o2 - * - * @return - */ - @Override - @SuppressWarnings("unchecked") - public int compare(Object o1, Object o2) - { - return super.compare(o1, o2) * -1; - } - } } diff --git a/scm-webapp/src/main/java/sonia/scm/security/RepositoryRole.java b/scm-webapp/src/main/java/sonia/scm/security/RepositoryRole.java index 6b6b06aa9c..1fab500d79 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/RepositoryRole.java +++ b/scm-webapp/src/main/java/sonia/scm/security/RepositoryRole.java @@ -1,7 +1,5 @@ package sonia.scm.security; -import org.apache.commons.collections.CollectionUtils; - import java.util.Collection; import java.util.Collections; import java.util.Objects; @@ -33,8 +31,9 @@ public class RepositoryRole { if (this == o) return true; if (!(o instanceof RepositoryRole)) return false; RepositoryRole that = (RepositoryRole) o; - return name.equals(that.name) && - CollectionUtils.isEqualCollection(this.verbs, that.verbs); + return name.equals(that.name) + && this.verbs.containsAll(that.verbs) + && this.verbs.size() == that.verbs.size(); } @Override From 614656a8b5e1f5cbba9e0ddae7bf49ab0561185f Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Thu, 31 Jan 2019 16:20:00 +0100 Subject: [PATCH 057/112] fix wrong version of jackson-jaxrs-json-provider --- scm-webapp/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 9f7e311820..45da9fec49 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -114,6 +114,12 @@ <version>${jackson.version}</version> </dependency> + <dependency> + <groupId>com.fasterxml.jackson.jaxrs</groupId> + <artifactId>jackson-jaxrs-json-provider</artifactId> + <version>${jackson.version}</version> + </dependency> + <!-- rest api --> <dependency> From 82aff173372c79496564c347d9109bb8b704797f Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Fri, 1 Feb 2019 07:30:31 +0000 Subject: [PATCH 058/112] Close branch bugfix/forbid_empty_groups_users_at_global_permission From 34542e5cf165185543ec379ab94a3ecd980e4d98 Mon Sep 17 00:00:00 2001 From: Mohamed Karray <mohamed.karray.sr@gmail.com> Date: Fri, 1 Feb 2019 08:51:07 +0100 Subject: [PATCH 059/112] fix i18n encoding --- scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java b/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java index b074781fec..6224b154a7 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java +++ b/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java @@ -78,6 +78,8 @@ public class I18nServlet extends HttpServlet { @VisibleForTesting @Override protected void doGet(HttpServletRequest req, HttpServletResponse response) { + response.setCharacterEncoding("UTF-8"); + response.setContentType("application/json"); try (PrintWriter out = response.getWriter()) { response.setContentType("application/json"); String path = req.getServletPath(); From 0a29f41835829ef110384b86a90f87525bc7feec Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Fri, 1 Feb 2019 09:42:19 +0100 Subject: [PATCH 060/112] verify encoding and content type --- .../src/main/java/sonia/scm/web/i18n/I18nServlet.java | 1 - .../test/java/sonia/scm/web/i18n/I18nServletTest.java | 9 +++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java b/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java index 6224b154a7..08a4a33493 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java +++ b/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java @@ -81,7 +81,6 @@ public class I18nServlet extends HttpServlet { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); try (PrintWriter out = response.getWriter()) { - response.setContentType("application/json"); String path = req.getServletPath(); Function<String, Optional<JsonNode>> jsonFileProvider = usedPath -> Optional.empty(); BiConsumer<String, JsonNode> createdJsonFileConsumer = (usedPath, jsonNode) -> log.debug("A json File is created from the path {}", usedPath); diff --git a/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java b/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java index a912f738e2..4874acab37 100644 --- a/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java +++ b/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java @@ -194,6 +194,8 @@ public class I18nServletTest { assertJson(json); verify(cache).get(path); verify(cache).put(eq(path), any()); + + verifyHeaders(response); } @Test @@ -221,6 +223,8 @@ public class I18nServletTest { verify(cache, never()).put(eq(path), any()); verify(cache).get(path); assertJson(json); + + verifyHeaders(response); } @Test @@ -234,6 +238,11 @@ public class I18nServletTest { assertJson(jsonNodeOptional.orElse(null)); } + private void verifyHeaders(HttpServletResponse response) { + verify(response).setCharacterEncoding("UTF-8"); + verify(response).setContentType("application/json"); + } + public void assertJson(JsonNode actual) throws IOException { assertJson(actual.toString()); } From c249d603d73da8e007a1d43f2859173a1e4b6c3f Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Fri, 1 Feb 2019 09:44:25 +0100 Subject: [PATCH 061/112] remove unused shiro rule and fixed some deprecated method calls --- .../sonia/scm/web/i18n/I18nServletTest.java | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java b/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java index 4874acab37..e028857e2c 100644 --- a/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java +++ b/scm-webapp/src/test/java/sonia/scm/web/i18n/I18nServletTest.java @@ -2,8 +2,6 @@ package sonia.scm.web.i18n; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.sdorra.shiro.ShiroRule; -import com.github.sdorra.shiro.SubjectAware; import com.google.common.base.Charsets; import com.google.common.io.Files; import org.apache.commons.lang3.StringUtils; @@ -42,12 +40,8 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.Silent.class) -@SubjectAware(configuration = "classpath:sonia/scm/shiro-001.ini") public class I18nServletTest { - @Rule - public ShiroRule shiro = new ShiroRule(); - private static final String GIT_PLUGIN_JSON = json( "{", "'scm-git-plugin': {", @@ -88,15 +82,15 @@ public class I18nServletTest { public TemporaryFolder temporaryFolder = new TemporaryFolder(); @Mock - PluginLoader pluginLoader; + private PluginLoader pluginLoader; @Mock - CacheManager cacheManager; + private CacheManager cacheManager; @Mock - ClassLoader classLoader; + private ClassLoader classLoader; - I18nServlet servlet; + private I18nServlet servlet; @Mock private Cache cache; @@ -106,9 +100,9 @@ public class I18nServletTest { @SuppressWarnings("unchecked") public void init() throws IOException { resources = Collections.enumeration(Lists.newArrayList( - createFileFromString(SVN_PLUGIN_JSON).toURL(), - createFileFromString(GIT_PLUGIN_JSON).toURL(), - createFileFromString(HG_PLUGIN_JSON).toURL() + createFileFromString(SVN_PLUGIN_JSON).toURI().toURL(), + createFileFromString(GIT_PLUGIN_JSON).toURI().toURL(), + createFileFromString(HG_PLUGIN_JSON).toURI().toURL() )); when(pluginLoader.getUberClassLoader()).thenReturn(classLoader); when(cacheManager.getCache(I18nServlet.CACHE_NAME)).thenReturn(cache); @@ -247,7 +241,7 @@ public class I18nServletTest { assertJson(actual.toString()); } - public void assertJson(String actual) throws IOException { + private void assertJson(String actual) throws IOException { assertThat(actual) .isNotEmpty() .contains(StringUtils.deleteWhitespace(GIT_PLUGIN_JSON.substring(1, GIT_PLUGIN_JSON.length() - 1))) @@ -255,7 +249,7 @@ public class I18nServletTest { .contains(StringUtils.deleteWhitespace(SVN_PLUGIN_JSON.substring(1, SVN_PLUGIN_JSON.length() - 1))); } - public File createFileFromString(String json) throws IOException { + private File createFileFromString(String json) throws IOException { File file = temporaryFolder.newFile(); Files.write(json.getBytes(Charsets.UTF_8), file); return file; From 1cedb26056b38c7398097681f54d0e0002cd7b60 Mon Sep 17 00:00:00 2001 From: Philipp Czora <philipp.czora@cloudogu.com> Date: Fri, 1 Feb 2019 10:04:37 +0100 Subject: [PATCH 062/112] ConfigurationBinder can now pass additional props to extensions --- .../ui-components/src/config/ConfigurationBinder.js | 10 +++++----- scm-ui/src/repos/containers/RepositoryRoot.js | 12 ++++++++---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js b/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js index 1b2b37bb19..96e8c630b8 100644 --- a/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js +++ b/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js @@ -36,9 +36,9 @@ class ConfigurationBinder { binder.bind("config.navigation", ConfigNavLink, configPredicate); // route for global configuration, passes the link from the index resource to component - const ConfigRoute = ({ url, links }) => { + const ConfigRoute = ({ url, links, ...additionalProps }) => { const link = links[linkName].href; - return this.route(url + to, <ConfigurationComponent link={link}/>); + return this.route(url + to, <ConfigurationComponent link={link} {...additionalProps} />); }; // bind config route to extension point @@ -63,9 +63,9 @@ class ConfigurationBinder { // route for global configuration, passes the current repository to component - const RepoRoute = ({url, repository}) => { - const link = repository._links[linkName].href - return this.route(url + to, <RepositoryComponent repository={repository} link={link}/>); + const RepoRoute = ({url, repository, ...additionalProps}) => { + const link = repository._links[linkName].href; + return this.route(url + to, <RepositoryComponent repository={repository} link={link} {...additionalProps}/>); }; // bind config route to extension point diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 07b6681752..4ab9382cc1 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -21,7 +21,7 @@ import ChangesetView from "./ChangesetView"; import PermissionsNavLink from "../components/PermissionsNavLink"; import Sources from "../sources/containers/Sources"; import RepositoryNavLink from "../components/RepositoryNavLink"; -import {getRepositoriesLink} from "../../modules/indexResource"; +import {getLinks, getRepositoriesLink} from "../../modules/indexResource"; import {ExtensionPoint} from "@scm-manager/ui-extensions"; type Props = { @@ -31,6 +31,7 @@ type Props = { loading: boolean, error: Error, repoLink: string, + indexLinks: Object, // dispatch functions fetchRepoByName: (link: string, namespace: string, name: string) => void, @@ -75,7 +76,7 @@ class RepositoryRoot extends React.Component<Props> { }; render() { - const { loading, error, repository, t } = this.props; + const { loading, error, indexLinks, repository, t } = this.props; if (error) { return ( @@ -95,7 +96,8 @@ class RepositoryRoot extends React.Component<Props> { const extensionProps = { repository, - url + url, + indexLinks }; return ( @@ -216,13 +218,15 @@ const mapStateToProps = (state, ownProps) => { const loading = isFetchRepoPending(state, namespace, name); const error = getFetchRepoFailure(state, namespace, name); const repoLink = getRepositoriesLink(state); + const indexLinks = getLinks(state); return { namespace, name, repository, loading, error, - repoLink + repoLink, + indexLinks }; }; From b5f392d73efe2cdfcadb6f296d7559091c91b8c5 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Fri, 1 Feb 2019 10:12:45 +0100 Subject: [PATCH 063/112] added sources overview --- .../src/repos/changesets/ChangesetList.js | 17 ++---- .../src/repos/sources/components/FileTree.js | 54 ++++++++++--------- .../src/repos/sources/containers/Content.js | 4 -- .../src/repos/sources/containers/Sources.js | 8 +-- .../repos/sources/containers/SourcesView.js | 17 ++---- scm-ui/styles/scm.scss | 39 +++++++------- 6 files changed, 59 insertions(+), 80 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetList.js b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetList.js index c50c8a2d50..e5fac920a2 100644 --- a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetList.js +++ b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetList.js @@ -1,26 +1,17 @@ // @flow import ChangesetRow from "./ChangesetRow"; import React from "react"; -import injectSheet from "react-jss"; -import classNames from "classnames"; import type { Changeset, Repository } from "@scm-manager/ui-types"; type Props = { repository: Repository, - changesets: Changeset[], - classes: any -}; - -const styles = { - toCenterContent: { - display: "block" - } + changesets: Changeset[] }; class ChangesetList extends React.Component<Props> { render() { - const { repository, changesets, classes } = this.props; + const { repository, changesets } = this.props; const content = changesets.map(changeset => { return ( <ChangesetRow @@ -31,11 +22,11 @@ class ChangesetList extends React.Component<Props> { ); }); return ( - <div className={classNames("panel-block", classes.toCenterContent)}> + <div className="panel-block"> {content} </div> ); } } -export default injectSheet(styles)(ChangesetList); +export default ChangesetList; diff --git a/scm-ui/src/repos/sources/components/FileTree.js b/scm-ui/src/repos/sources/components/FileTree.js index cbe03df62f..18ef1b01c5 100644 --- a/scm-ui/src/repos/sources/components/FileTree.js +++ b/scm-ui/src/repos/sources/components/FileTree.js @@ -108,32 +108,34 @@ class FileTree extends React.Component<Props> { } return ( - <table className="table table-hover table-sm is-fullwidth"> - <thead> - <tr> - <th className={classes.iconColumn} /> - <th>{t("sources.file-tree.name")}</th> - <th className="is-hidden-mobile"> - {t("sources.file-tree.length")} - </th> - <th className="is-hidden-mobile"> - {t("sources.file-tree.lastModified")} - </th> - <th className="is-hidden-mobile"> - {t("sources.file-tree.description")} - </th> - </tr> - </thead> - <tbody> - {files.map(file => ( - <FileTreeLeaf - key={file.name} - file={file} - baseUrl={baseUrlWithRevision} - /> - ))} - </tbody> - </table> + <div className="panel-block"> + <table className="table table-hover table-sm is-fullwidth"> + <thead> + <tr> + <th className={classes.iconColumn} /> + <th>{t("sources.file-tree.name")}</th> + <th className="is-hidden-mobile"> + {t("sources.file-tree.length")} + </th> + <th className="is-hidden-mobile"> + {t("sources.file-tree.lastModified")} + </th> + <th className="is-hidden-mobile"> + {t("sources.file-tree.description")} + </th> + </tr> + </thead> + <tbody> + {files.map(file => ( + <FileTreeLeaf + key={file.name} + file={file} + baseUrl={baseUrlWithRevision} + /> + ))} + </tbody> + </table> + </div> ); } } diff --git a/scm-ui/src/repos/sources/containers/Content.js b/scm-ui/src/repos/sources/containers/Content.js index cc4aa32b5b..899e40b511 100644 --- a/scm-ui/src/repos/sources/containers/Content.js +++ b/scm-ui/src/repos/sources/containers/Content.js @@ -29,9 +29,6 @@ type State = { }; const styles = { - toCenterContent: { - display: "block" - }, pointer: { cursor: "pointer" }, @@ -126,7 +123,6 @@ class Content extends React.Component<Props, State> { <div className={classNames( "panel-block", - classes.toCenterContent, classes.hasBackground )} > diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index 321c1faa16..7da024b450 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -93,15 +93,17 @@ class Sources extends React.Component<Props> { if (currentFileIsDirectory) { return ( - <div className="has-border-around is-round"> - {this.renderBranchSelector()} + <nav className="panel"> + <article className="panel-heading"> + {this.renderBranchSelector()} + </article> <FileTree repository={repository} revision={revision} path={path} baseUrl={baseUrl} /> - </div> + </nav> ); } else { return ( diff --git a/scm-ui/src/repos/sources/containers/SourcesView.js b/scm-ui/src/repos/sources/containers/SourcesView.js index 220c6ecc27..0f729beb2e 100644 --- a/scm-ui/src/repos/sources/containers/SourcesView.js +++ b/scm-ui/src/repos/sources/containers/SourcesView.js @@ -8,15 +8,12 @@ import { ExtensionPoint } from "@scm-manager/ui-extensions"; import { getContentType } from "./contentType"; import type { File, Repository } from "@scm-manager/ui-types"; import { ErrorNotification, Loading } from "@scm-manager/ui-components"; -import injectSheet from "react-jss"; -import classNames from "classnames"; type Props = { repository: Repository, file: File, revision: string, - path: string, - classes: any + path: string }; type State = { @@ -26,12 +23,6 @@ type State = { error?: Error }; -const styles = { - toCenterContent: { - display: "block" - } -}; - class SourcesView extends React.Component<Props, State> { constructor(props: Props) { super(props); @@ -87,7 +78,7 @@ class SourcesView extends React.Component<Props, State> { } render() { - const { file, classes } = this.props; + const { file } = this.props; const { loaded, error } = this.state; if (!file || !loaded) { @@ -99,8 +90,8 @@ class SourcesView extends React.Component<Props, State> { const sources = this.showSources(); - return <div className={classNames("panel-block", classes.toCenterContent)}>{sources}</div>; + return <div className="panel-block">{sources}</div>; } } -export default injectSheet(styles)(SourcesView); +export default SourcesView; diff --git a/scm-ui/styles/scm.scss b/scm-ui/styles/scm.scss index 59f1ca07a4..258c8a84a4 100644 --- a/scm-ui/styles/scm.scss +++ b/scm-ui/styles/scm.scss @@ -85,15 +85,6 @@ $fa-font-path: "webfonts"; } } -//border around options -.has-border-around { - border: 1px solid #dbdbdb; - - &.is-round { - border-radius: 4px; - } -} - // multiline Columns .columns.is-multiline { .column.is-half { @@ -195,20 +186,26 @@ $fa-font-path: "webfonts"; } //panels -.panel-footer { - background-color: whitesmoke; - border-radius: 0 0 4px 4px; - color: #363636; - font-size: 1.25em; - font-weight: 300; - line-height: 1.25; - padding: 0.5em 0.75em; +nav.panel { + .panel-block { + display: block; + } - border-left: 1px solid #dbdbdb; - border-right: 1px solid #dbdbdb; + .panel-footer { + background-color: whitesmoke; + border-radius: 0 0 4px 4px; + color: #363636; + font-size: 1.25em; + font-weight: 300; + line-height: 1.25; + padding: 0.5em 0.75em; - &:last-child { - border-bottom: 1px solid #dbdbdb; + border-left: 1px solid #dbdbdb; + border-right: 1px solid #dbdbdb; + + &:last-child { + border-bottom: 1px solid #dbdbdb; + } } } From 59abab212a5c2c188062b586f686f9532a668215 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Fri, 1 Feb 2019 10:50:05 +0100 Subject: [PATCH 064/112] fixed changesets component --- .../src/repos/changesets/ChangesetList.js | 6 +--- scm-ui/src/repos/containers/Changesets.js | 33 +++++++++++++++---- .../repos/sources/containers/HistoryView.js | 4 ++- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetList.js b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetList.js index e5fac920a2..4cd8ec319c 100644 --- a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetList.js +++ b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetList.js @@ -21,11 +21,7 @@ class ChangesetList extends React.Component<Props> { /> ); }); - return ( - <div className="panel-block"> - {content} - </div> - ); + return <>{content}</>; } } diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index 69dc41da2d..5edc677e60 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -1,8 +1,13 @@ // @flow import React from "react"; -import {withRouter} from "react-router-dom"; -import type {Branch, Changeset, PagedCollection, Repository} from "@scm-manager/ui-types"; +import { withRouter } from "react-router-dom"; +import type { + Branch, + Changeset, + PagedCollection, + Repository +} from "@scm-manager/ui-types"; import { fetchChangesets, getChangesets, @@ -11,9 +16,15 @@ import { selectListAsCollection } from "../modules/changesets"; -import {connect} from "react-redux"; -import {ErrorNotification, getPageFromMatch, LinkPaginator, ChangesetList, Loading} from "@scm-manager/ui-components"; -import {compose} from "redux"; +import { connect } from "react-redux"; +import { + ErrorNotification, + getPageFromMatch, + LinkPaginator, + ChangesetList, + Loading +} from "@scm-manager/ui-components"; +import { compose } from "redux"; type Props = { repository: Repository, @@ -64,13 +75,21 @@ class Changesets extends React.Component<Props> { renderList = () => { const { repository, changesets } = this.props; - return <ChangesetList repository={repository} changesets={changesets} />; + return ( + <div className="panel-block"> + <ChangesetList repository={repository} changesets={changesets} /> + </div> + ); }; renderPaginator = () => { const { page, list } = this.props; if (list) { - return <div className="panel-footer"><LinkPaginator page={page} collection={list} /></div>; + return ( + <div className="panel-footer"> + <LinkPaginator page={page} collection={list} /> + </div> + ); } return null; }; diff --git a/scm-ui/src/repos/sources/containers/HistoryView.js b/scm-ui/src/repos/sources/containers/HistoryView.js index c118ef9cea..d13b5904d2 100644 --- a/scm-ui/src/repos/sources/containers/HistoryView.js +++ b/scm-ui/src/repos/sources/containers/HistoryView.js @@ -79,7 +79,9 @@ class HistoryView extends React.Component<Props, State> { const currentPage = page + 1; return ( <> - <ChangesetList repository={repository} changesets={changesets} /> + <div className="panel-block"> + <ChangesetList repository={repository} changesets={changesets} /> + </div> <div className="panel-footer"> <StatePaginator page={currentPage} From c5c66ef4ea87568826e69e69bad2c41eb2c9dede Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Fri, 1 Feb 2019 09:55:03 +0000 Subject: [PATCH 065/112] Close branch bugfix/encoding_i18n_servlet From 0317e659927dce3ac98e52ca4a5a1f95daaa5069 Mon Sep 17 00:00:00 2001 From: Mohamed Karray <mohamed.karray.sr@gmail.com> Date: Fri, 1 Feb 2019 09:59:07 +0000 Subject: [PATCH 066/112] Close branch feature/additionalPropsToExtensionBranchWp From 0319bdf8f03ea4d0d8b79aea88904e82174a3a26 Mon Sep 17 00:00:00 2001 From: Iwan Schindler <iwan.schindler@cloudogu.com> Date: Fri, 1 Feb 2019 11:45:44 +0100 Subject: [PATCH 067/112] i18n for core plugins --- .../main/resources/locales/de/plugins.json | 52 +++++++++++- .../main/resources/locales/de/plugins.json | 45 ++++++++++- .../main/resources/locales/en/plugins.json | 4 +- .../main/resources/locales/de/plugins.json | 37 ++++++++- .../main/resources/locales/en/plugins.json | 6 +- .../main/resources/locales/de/plugins.json | 81 +++++++++++++++++++ 6 files changed, 213 insertions(+), 12 deletions(-) create mode 100644 scm-webapp/src/main/resources/locales/de/plugins.json diff --git a/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json b/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json index 1dc0e254c2..8902a57f32 100644 --- a/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json +++ b/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json @@ -1,9 +1,55 @@ { "scm-git-plugin": { "information": { - "clone" : "Repository Klonen", - "create" : "Neue Repository erstellen", - "replace" : "Eine existierende Repository aktualisieren" + "clone" : "Repository klonen", + "create" : "Neues Repository erstellen", + "replace" : "Ein bestehendes Repository aktualisieren", + "merge": { + "heading": "Merge des Source Branch in den Target Branch", + "checkout": "1. Sicherstellen, dass der Workspace aufgeräumt ist und der Target Branch ausgecheckt wurde.", + "update": "2. Update Workspace", + "merge": "3. Merge Source Branch", + "resolve": "4. Merge Konflikte auflösen und korrigierte Dateien dem Index hinzufügen.", + "commit": "5. Commit", + "push": "6. Push des Merge" + } + }, + "config": { + "link": "Git", + "title": "Git Konfiguration", + "gcExpression": "GC Cron Ausdruck", + "gcExpressionHelpText": "Benutze Quartz Cron Ausdrücke (SECOND MINUTE HOUR DAYOFMONTH MONTH DAYOFWEEK), um git GC regelmäßig auszuführen.", + "nonFastForwardDisallowed": "Deaktiviere \"Non Fast-Forward\"", + "nonFastForwardDisallowedHelpText": "Git Pushes ablehnen, die nicht \"fast-forward\" sind, wie \"--force\".", + "disabled": "Deaktiviert", + "disabledHelpText": "Aktiviere oder deaktiviere das Git Plugin", + "submit": "Speichern" + }, + "repo-config": { + "link": "Konfiguration", + "default-branch": "Standard Branch", + "submit": "Speichern", + "error": { + "title": "Fehler", + "subtitle": "Ein Fehler ist aufgetreten." + }, + "success": "Der standard Branch wurde geändert!" + } + }, + "permissions" : { + "configuration": { + "read": { + "git": { + "displayName": "Git Konfiguration lesen", + "description": "Darf die git Konfiguration lesen." + } + }, + "write": { + "git": { + "displayName": "Git Konfiguration schreiben", + "description": "Darf die git Konfiguration verändern." + } + } } } } diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json b/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json index 0824a4ad38..37d6d4be2a 100644 --- a/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json +++ b/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json @@ -1,9 +1,48 @@ { "scm-hg-plugin": { "information": { - "clone" : "Repository Klonen", - "create" : "Neue Repository erstellen", - "replace" : "Eine existierende Repository aktualisieren" + "clone" : "Repository klonen", + "create" : "Neues Repository erstellen", + "replace" : "Ein bestehendes Repository aktualisieren" + }, + "config": { + "link": "Mercurial", + "title": "Mercurial Konfiguration", + "hgBinary": "HG Binary", + "hgBinaryHelpText": "Pfad des Mercurial Binary.", + "pythonBinary": "Python Binary", + "pythonBinaryHelpText": "Pfad des Python binary.", + "pythonPath": "Python Module Such Pfad", + "pythonPathHelpText": "Python Module Such Pfad (PYTHONPATH).", + "encoding": "Encoding", + "encodingHelpText": "Repository Encoding.", + "useOptimizedBytecode": "Optimized Bytecode (.pyo)", + "useOptimizedBytecodeHelpText": "Verwende den Python '-O' Switch.", + "showRevisionInId": "Revision anzeigen", + "showRevisionInIdHelpText": "Die Revision als Teil der Node ID anzeigen.", + "enableHttpPostArgs": "HttpPostArgs Protocol aktivieren", + "enableHttpPostArgsHelpText": "Aktiviert das experimentelle HttpPostArgs Protokoll von Mercurial. Das HttpPostArgs Protokoll verwendet den Post Request Body anstatt des HTTP Headers um Meta Informationen zu versenden. Dieses Vorgehen reduziert die Header Größe der Mercurial Requests. HttpPostArgs wird seit Mercurial 3.8 unterstützt.", + "disableHookSSLValidation": "SSL Validierung für Hooks deaktivieren", + "disableHookSSLValidationHelpText": "Deaktiviert die Validierung von SSL Zertifikaten für den Mercurial Hook, der die Repositoryänderungen wieder zurück an den SCM-Manager leitet. Diese Option sollte nur benutzt werden, wenn der SCM-Manager ein selbstsigniertes Zertifikat verwendet.", + "disabled": "Deaktiviert", + "disabledHelpText": "Aktiviert oder deaktiviert das Mercurial Plugin.", + "required": "Dieser Konfigurationswert wird benötigt" + } + }, + "permissions" : { + "configuration": { + "read": { + "hg": { + "displayName": "Mercurial Konfiguration lesen", + "description": "Darf die Mercurial Konfiguration lesen" + } + }, + "write": { + "hg": { + "displayName": "Mercurial Konfiguration schreiben", + "description": "Darf die Mercurial Konfiguration verändern" + } + } } } } diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json b/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json index ee1b6c8911..61340ab9cf 100644 --- a/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json +++ b/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json @@ -21,9 +21,9 @@ "showRevisionInId": "Show Revision", "showRevisionInIdHelpText": "Show revision as part of the node id.", "enableHttpPostArgs": "Enable HttpPostArgs Protocol", - "enableHttpPostArgsHelpText": "Disables the validation of ssl certificates for the mercurial hook, which forwards the repository changes back to scm-manager. This option should only be used, if SCM-Manager uses a self signed certificate.", + "enableHttpPostArgsHelpText": "Enables the experimental HttpPostArgs Protocol of mercurial. The HttpPostArgs Protocol uses the body of post requests to send the meta information instead of http headers. This helps to reduce the header size of mercurial requests. HttpPostArgs is supported since mercurial 3.8.", "disableHookSSLValidation": "Disable SSL Validation on Hooks", - "disableHookSSLValidationHelpText": "Enables the experimental HttpPostArgs Protocol of mercurial. The HttpPostArgs Protocol uses the body of post requests to send the meta information instead of http headers. This helps to reduce the header size of mercurial requests. HttpPostArgs is supported since mercurial 3.8.", + "disableHookSSLValidationHelpText": "Disables the validation of ssl certificates for the mercurial hook, which forwards the repository changes back to scm-manager. This option should only be used, if SCM-Manager uses a self signed certificate.", "disabled": "Disabled", "disabledHelpText": "Enable or disable the Mercurial plugin.", "required": "This configuration value is required" diff --git a/scm-plugins/scm-svn-plugin/src/main/resources/locales/de/plugins.json b/scm-plugins/scm-svn-plugin/src/main/resources/locales/de/plugins.json index 7c58498ef1..58a18482b2 100644 --- a/scm-plugins/scm-svn-plugin/src/main/resources/locales/de/plugins.json +++ b/scm-plugins/scm-svn-plugin/src/main/resources/locales/de/plugins.json @@ -1,7 +1,42 @@ { "scm-svn-plugin": { "information": { - "checkout" : "Repository auschecken" + "checkout": "Repository auschecken" + }, + "config": { + "link": "Subversion", + "title": "Subversion Konfiguration", + "compatibility": "Version Kompatibilität", + "compatibilityHelpText": "Gibt an, mit welcher Subversion Version die Repositories kompatibel sind.", + "compatibility-values": { + "none": "Keine Kompatibilität", + "pre14": "Vor 1.4 kompatibel", + "pre15": "Vor 1.5 kompatibel", + "pre16": "Vor 1.6 kompatibel", + "pre17": "Vor 1.7 kompatibel", + "with17": "Mit 1.7 kompatibel" + }, + "enabledGZip": "GZip Compression aktivieren", + "enabledGZipHelpText": "Aktiviert GZip Kompression für SVN Responses", + "disabled": "Deaktiviert", + "disabledHelpText": "Aktiviert oder deaktiviert das SVN Plugin", + "required": "Dieser Konfigurationswert wird benötigt" + } + }, + "permissions": { + "configuration": { + "read": { + "svn": { + "displayName": "Subversion Konfiguration lesen", + "description": "Darf die Subversion Konfiguration lesen" + } + }, + "write": { + "svn": { + "displayName": "Subversion Konfiguration schreiben", + "description": "Darf die Subversion Konfiguration verändern" + } + } } } } diff --git a/scm-plugins/scm-svn-plugin/src/main/resources/locales/en/plugins.json b/scm-plugins/scm-svn-plugin/src/main/resources/locales/en/plugins.json index 2a363c77cd..a796027afc 100644 --- a/scm-plugins/scm-svn-plugin/src/main/resources/locales/en/plugins.json +++ b/scm-plugins/scm-svn-plugin/src/main/resources/locales/en/plugins.json @@ -7,7 +7,7 @@ "link": "Subversion", "title": "Subversion Configuration", "compatibility": "Version Compatibility", - "compatibilityHelpText": "Specifies with which subversion version repositories are compatible.", + "compatibilityHelpText": "Specifies with which Subversion version repositories are compatible.", "compatibility-values": { "none": "No compatibility", "pre14": "Pre 1.4 Compatible", @@ -17,9 +17,9 @@ "with17": "With 1.7 Compatible" }, "enabledGZip": "Enable GZip Compression", - "enabledGZipHelpText": "Enable GZip compression for svn responses.", + "enabledGZipHelpText": "Enable GZip compression for SVN responses.", "disabled": "Disabled", - "disabledHelpText": "Enable or disable the Git plugin", + "disabledHelpText": "Enable or disable the SVN plugin", "required": "This configuration value is required" } }, diff --git a/scm-webapp/src/main/resources/locales/de/plugins.json b/scm-webapp/src/main/resources/locales/de/plugins.json new file mode 100644 index 0000000000..96eb8e8e9b --- /dev/null +++ b/scm-webapp/src/main/resources/locales/de/plugins.json @@ -0,0 +1,81 @@ +{ + "permissions": { + "repository": { + "read,pull": { + "*": { + "displayName": "Alle Repositories lesen", + "description": "Darf alle Repositories lesen und klonen." + } + }, + "read,pull,push": { + "*": { + "displayName": "Alle Repositories schreiben", + "description": "Darf alle Repositories lesen, klonen und schreiben." + } + }, + "*": { + "*": { + "displayName": "Alle Repositories besitzen (Owner)", + "description": "Darf alle Repositories lesen, klonen, schreiben, konfigurieren und löschen." + } + }, + "create": { + "displayName": "Repositories erstellen", + "description": "Darf Repositories erstellen." + } + }, + "user": { + "*": { + "displayName": "Benutzer administrieren", + "description": "Darf Benutzer administrieren." + } + }, + "group": { + "*": { + "displayName": "Gruppen administrieren", + "description": "Darf Gruppen administrieren." + } + }, + "unknown": "Unbekannte Berechtigung" + }, + "verbs": { + "repository": { + "read": { + "displayName": "Lesen", + "description": "Darf das Repository im SCM-Manager sehen." + }, + "modify": { + "displayName": "Modifizieren", + "description": "Darf die Eigenschaften des Repository verändern." + }, + "delete": { + "displayName": "Löschen", + "description": "Darf das Repository löschen." + }, + "pull": { + "displayName": "Pull/Checkout", + "description": "Darf pull/checkout auf das Repository ausführen." + }, + "push": { + "displayName": "Push/Commit", + "description": "Darf push/commit auf das Repository ausführen und damit den Inhalt verändern." + }, + "permissionRead": { + "displayName": "Berechtigungen lesen", + "description": "Darf die Berechtigungen des Repository sehen." + }, + "permissionWrite": { + "displayName": "Berechtigungen modifizieren", + "description": "Darf die Berechtigungen des Repository bearbeiten." + }, + "healthCheck": { + "displayName": "Health Check", + "description": "Darf den Repository Health Check ausführen." + }, + "*": { + "displayName": "Alle Repository Rechte", + "description": "Darf im Repository Kontext alles ausführen. Dies beinhaltet alle Repository Berechtigungen." + } + } + } +} From a09698d526fff63a1452e40c88ae24e3e9091a18 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Fri, 1 Feb 2019 13:51:14 +0100 Subject: [PATCH 068/112] fixed smaller merge issues --- scm-ui/src/repos/containers/RepositoryRoot.js | 5 ----- scm-ui/src/users/components/UserForm.js | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 2c2d4b4ace..d0d722e1fd 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -33,13 +33,8 @@ import ChangesetView from "./ChangesetView"; import PermissionsNavLink from "../components/PermissionsNavLink"; import Sources from "../sources/containers/Sources"; import RepositoryNavLink from "../components/RepositoryNavLink"; -<<<<<<< working copy -import { getRepositoriesLink } from "../../modules/indexResource"; -import { ExtensionPoint } from "@scm-manager/ui-extensions"; -======= import {getLinks, getRepositoriesLink} from "../../modules/indexResource"; import {ExtensionPoint} from "@scm-manager/ui-extensions"; ->>>>>>> merge rev type Props = { namespace: string, diff --git a/scm-ui/src/users/components/UserForm.js b/scm-ui/src/users/components/UserForm.js index d2d827aa93..c8fae1ff0a 100644 --- a/scm-ui/src/users/components/UserForm.js +++ b/scm-ui/src/users/components/UserForm.js @@ -141,9 +141,9 @@ class UserForm extends React.Component<Props, State> { <> {subtitle} <form onSubmit={this.submit}> - <div className="columns"> + <div className="columns is-multiline"> + {nameField} <div className="column is-half"> - {nameField} <InputField label={t("user.displayName")} onChange={this.handleDisplayNameChange} From de8b6dbd84c92b8fc3c3bb75ecb0e7774c1f4356 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Fri, 1 Feb 2019 13:59:01 +0100 Subject: [PATCH 069/112] fixed smaller merge issues --- scm-ui/public/locales/en/repos.json | 2 +- scm-ui/public/locales/en/users.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 305f182ccb..4aabcf6c72 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -111,7 +111,7 @@ "name-input-invalid": "Permission is not allowed to be empty! If it is not empty, your input name is invalid or it already exists!" }, "help": { - "groupPermissionHelpText": "States if a permission is a group permission.", + "groupPermissionHelpText": "States if a permission is a group permission. If this is not checked, it is a user permission.", "nameHelpText": "Manage permissions for a specific user or group.", "roleHelpText": "READ = read; WRITE = read and write; OWNER = read, write and also the ability to manage the properties and permissions. If nothing is selected here, use the 'Advanced' Button to see detailed permissions.", "permissionsHelpText": "Use this to specify your own set of permissions regardless of predefined roles." diff --git a/scm-ui/public/locales/en/users.json b/scm-ui/public/locales/en/users.json index f13f68df2c..2b72d85cbc 100644 --- a/scm-ui/public/locales/en/users.json +++ b/scm-ui/public/locales/en/users.json @@ -20,7 +20,7 @@ "displayNameHelpText": "Display name of the user.", "mailHelpText": "Email address of the user.", "adminHelpText": "An administrator is able to create, modify and delete repositories, groups and users.", - "activeHelpText": "Activate or deactive the user." + "activeHelpText": "Activate or deactivate the user." }, "users": { "title": "Users", From e8666fa3e861d9c9b1935603e7c9ff0158a6ac0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= <rene.pfeuffer@cloudogu.com> Date: Fri, 1 Feb 2019 15:53:13 +0100 Subject: [PATCH 070/112] Add disabled flag to download button --- .../packages/ui-components/src/buttons/DownloadButton.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/buttons/DownloadButton.js b/scm-ui-components/packages/ui-components/src/buttons/DownloadButton.js index 279c472f0e..ac24d68447 100644 --- a/scm-ui-components/packages/ui-components/src/buttons/DownloadButton.js +++ b/scm-ui-components/packages/ui-components/src/buttons/DownloadButton.js @@ -3,14 +3,15 @@ import React from "react"; type Props = { displayName: string, - url: string + url: string, + disabled: boolean }; class DownloadButton extends React.Component<Props> { render() { - const { displayName, url } = this.props; + const { displayName, url, disabled } = this.props; return ( - <a className="button is-large is-link" href={url}> + <a className="button is-large is-link" href={url} disabled={disabled}> <span className="icon is-medium"> <i className="fas fa-arrow-circle-down" /> </span> From 1c459e994473cc2381dfccab7899da6aa01264eb Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Fri, 1 Feb 2019 16:08:33 +0100 Subject: [PATCH 071/112] renamed editusernavlink back + implemented new extension point for repo --- .../src/config/ConfigurationBinder.js | 29 ++++++++++++++++++- ...avLink.test.js => EditUserNavLink.test.js} | 0 2 files changed, 28 insertions(+), 1 deletion(-) rename scm-ui/src/users/components/navLinks/{GeneralUserNavLink.test.js => EditUserNavLink.test.js} (100%) diff --git a/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js b/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js index 2454ad9cea..da1372c316 100644 --- a/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js +++ b/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js @@ -59,7 +59,7 @@ class ConfigurationBinder { }); // bind navigation link to extension point - binder.bind("repository.subnavigation", RepoNavLink, repoPredicate); + binder.bind("repository.navigation", RepoNavLink, repoPredicate); // route for global configuration, passes the current repository to component @@ -72,6 +72,33 @@ class ConfigurationBinder { binder.bind("repository.route", RepoRoute, repoPredicate); } + bindRepositorySub(to: string, labelI18nKey: string, linkName: string, RepositoryComponent: any) { + + // create predicate based on the link name of the current repository route + // if the linkname is not available, the navigation link and the route are not bound to the extension points + const repoPredicate = (props: Object) => { + return props.repository && props.repository._links && props.repository._links[linkName]; + }; + + // create NavigationLink with translated label + const RepoNavLink = translate(this.i18nNamespace)(({t, url}) => { + return this.navLink(url + "/settings" + to, labelI18nKey, t); + }); + + // bind navigation link to extension point + binder.bind("repository.subnavigation", RepoNavLink, repoPredicate); + + + // route for global configuration, passes the current repository to component + const RepoRoute = ({url, repository, ...additionalProps}) => { + const link = repository._links[linkName].href; + return this.route(url + "/settings" + to, <RepositoryComponent repository={repository} link={link} {...additionalProps}/>); + }; + + // bind config route to extension point + binder.bind("repository.route", RepoRoute, repoPredicate); + } + } export default new ConfigurationBinder(); diff --git a/scm-ui/src/users/components/navLinks/GeneralUserNavLink.test.js b/scm-ui/src/users/components/navLinks/EditUserNavLink.test.js similarity index 100% rename from scm-ui/src/users/components/navLinks/GeneralUserNavLink.test.js rename to scm-ui/src/users/components/navLinks/EditUserNavLink.test.js From 7baea043c1ec581e0156357e63bc79783b5ee48e Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Fri, 1 Feb 2019 16:13:28 +0100 Subject: [PATCH 072/112] fix for git plugin bind --- scm-plugins/scm-git-plugin/src/main/js/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/js/index.js b/scm-plugins/scm-git-plugin/src/main/js/index.js index 5534d2061a..d3fe67476b 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/index.js +++ b/scm-plugins/scm-git-plugin/src/main/js/index.js @@ -27,8 +27,8 @@ binder.bind( ); binder.bind("repos.repository-avatar", GitAvatar, gitPredicate); -cfgBinder.bindRepository( - "/settings/configuration", +cfgBinder.bindRepositorySub( + "/configuration", "scm-git-plugin.repo-config.link", "configuration", RepositoryConfig From ccb199abb75a79e884da88438beccfe63b87a859 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Fri, 1 Feb 2019 17:03:00 +0100 Subject: [PATCH 073/112] renamed panel blocks --- scm-ui/src/repos/containers/ChangesetsRoot.js | 8 ++++---- scm-ui/src/repos/sources/containers/Content.js | 6 +++--- scm-ui/src/repos/sources/containers/Sources.js | 8 ++++---- scm-ui/styles/scm.scss | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/scm-ui/src/repos/containers/ChangesetsRoot.js b/scm-ui/src/repos/containers/ChangesetsRoot.js index d3d4635e18..0cbf4f1970 100644 --- a/scm-ui/src/repos/containers/ChangesetsRoot.js +++ b/scm-ui/src/repos/containers/ChangesetsRoot.js @@ -89,12 +89,12 @@ class BranchRoot extends React.Component<Props> { const changesets = <Changesets repository={repository} branch={branch} />; return ( - <nav className="panel"> - <article className="panel-heading"> + <div className="panel"> + <div className="panel-heading"> {this.renderBranchSelector()} - </article> + </div> <Route path={`${url}/:page?`} component={() => changesets} /> - </nav> + </div> ); } diff --git a/scm-ui/src/repos/sources/containers/Content.js b/scm-ui/src/repos/sources/containers/Content.js index 899e40b511..d1b8fc8021 100644 --- a/scm-ui/src/repos/sources/containers/Content.js +++ b/scm-ui/src/repos/sources/containers/Content.js @@ -176,11 +176,11 @@ class Content extends React.Component<Props, State> { return ( <div> - <nav className="panel"> - <article className="panel-heading">{header}</article> + <div className="panel"> + <div className="panel-heading">{header}</div> {moreInformation} {content} - </nav> + </div> </div> ); } diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index 7da024b450..0d038ffdbe 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -93,17 +93,17 @@ class Sources extends React.Component<Props> { if (currentFileIsDirectory) { return ( - <nav className="panel"> - <article className="panel-heading"> + <div className="panel"> + <div className="panel-heading"> {this.renderBranchSelector()} - </article> + </div> <FileTree repository={repository} revision={revision} path={path} baseUrl={baseUrl} /> - </nav> + </div> ); } else { return ( diff --git a/scm-ui/styles/scm.scss b/scm-ui/styles/scm.scss index 258c8a84a4..1feffc401f 100644 --- a/scm-ui/styles/scm.scss +++ b/scm-ui/styles/scm.scss @@ -186,7 +186,7 @@ $fa-font-path: "webfonts"; } //panels -nav.panel { +.panel { .panel-block { display: block; } From 6986ab86d28b80b10d36e27e3a80cd37909268b2 Mon Sep 17 00:00:00 2001 From: Mohamed Karray <mohamed.karray.sr@gmail.com> Date: Fri, 1 Feb 2019 17:22:10 +0100 Subject: [PATCH 074/112] add extension point for the first primary navigation menu --- .../ui-components/src/navigation/PrimaryNavigation.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scm-ui-components/packages/ui-components/src/navigation/PrimaryNavigation.js b/scm-ui-components/packages/ui-components/src/navigation/PrimaryNavigation.js index f2401729d6..897c63138e 100644 --- a/scm-ui-components/packages/ui-components/src/navigation/PrimaryNavigation.js +++ b/scm-ui-components/packages/ui-components/src/navigation/PrimaryNavigation.js @@ -50,8 +50,19 @@ class PrimaryNavigation extends React.Component<Props> { createNavigationItems = () => { const navigationItems = []; + const { t, links } = this.props; + + const props = { + links, + label: t("primary-navigation.first-menu") + }; const append = this.createNavigationAppender(navigationItems); + if (binder.hasExtension("primary-navigation.first-menu", props)) { + navigationItems.push( + <ExtensionPoint name="primary-navigation.first-menu" props={props} /> + ); + } append("/repos", "/(repo|repos)", "primary-navigation.repositories", "repositories"); append("/users", "/(user|users)", "primary-navigation.users", "users"); append("/groups", "/(group|groups)", "primary-navigation.groups", "groups"); From 5432ee56dc00759629c58c400457f9df71358f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= <rene.pfeuffer@cloudogu.com> Date: Fri, 1 Feb 2019 18:20:50 +0100 Subject: [PATCH 075/112] Add optional callback to download button --- .../packages/ui-components/src/buttons/DownloadButton.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/buttons/DownloadButton.js b/scm-ui-components/packages/ui-components/src/buttons/DownloadButton.js index ac24d68447..382bd500c5 100644 --- a/scm-ui-components/packages/ui-components/src/buttons/DownloadButton.js +++ b/scm-ui-components/packages/ui-components/src/buttons/DownloadButton.js @@ -4,14 +4,16 @@ import React from "react"; type Props = { displayName: string, url: string, - disabled: boolean + disabled: boolean, + onClick?: () => void }; class DownloadButton extends React.Component<Props> { render() { - const { displayName, url, disabled } = this.props; + const { displayName, url, disabled, onClick } = this.props; + const onClickOrDefault = !!onClick ? onClick : () => {}; return ( - <a className="button is-large is-link" href={url} disabled={disabled}> + <a className="button is-large is-link" href={url} disabled={disabled} onClick={onClickOrDefault}> <span className="icon is-medium"> <i className="fas fa-arrow-circle-down" /> </span> From 5ac065e5dac68e92eb8bf5e16a06435c857d8832 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Mon, 4 Feb 2019 11:39:59 +0100 Subject: [PATCH 076/112] separate methods for reading modification from transaction and from revision --- .../spi/SvnModificationsCommand.java | 56 +++++++++++-------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModificationsCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModificationsCommand.java index bbcaa9a9d5..580bc0b77d 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModificationsCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModificationsCommand.java @@ -21,41 +21,51 @@ public class SvnModificationsCommand extends AbstractSvnCommand implements Modif super(context, repository); } - @Override - @SuppressWarnings("unchecked") - public Modifications getModifications(String revision) { - final Modifications modifications = new Modifications(); - log.debug("get modifications {}", revision); + public Modifications getModifications(String revisionOrTransactionId) { + Modifications modifications; try { - if (SvnUtil.isTransactionEntryId(revision)) { - - SVNLookClient client = SVNClientManager.newInstance().getLookClient(); - client.doGetChanged(context.getDirectory(), SvnUtil.getTransactionId(revision), - e -> SvnUtil.appendModification(modifications, e.getType(), e.getPath()), true); - - return modifications; - + if (SvnUtil.isTransactionEntryId(revisionOrTransactionId)) { + modifications = getModificationsFromTransaction(SvnUtil.getTransactionId(revisionOrTransactionId)); } else { - - long revisionNumber = SvnUtil.getRevisionNumber(revision, repository); - SVNRepository repo = open(); - Collection<SVNLogEntry> entries = repo.log(null, null, revisionNumber, - revisionNumber, true, true); - if (Util.isNotEmpty(entries)) { - return SvnUtil.createModifications(entries.iterator().next(), revision); - } + modifications = getModificationFromRevision(revisionOrTransactionId); } + return modifications; } catch (SVNException ex) { - throw new InternalRepositoryException(repository, "could not open repository", ex); + throw new InternalRepositoryException( + repository, + "failed to get svn modifications for " + revisionOrTransactionId, + ex + ); + } + } + + @SuppressWarnings("unchecked") + private Modifications getModificationFromRevision(String revision) throws SVNException { + log.debug("get svn modifications from revision: {}", revision); + long revisionNumber = SvnUtil.getRevisionNumber(revision, repository); + SVNRepository repo = open(); + Collection<SVNLogEntry> entries = repo.log(null, null, revisionNumber, + revisionNumber, true, true); + if (Util.isNotEmpty(entries)) { + return SvnUtil.createModifications(entries.iterator().next(), revision); } return null; } + private Modifications getModificationsFromTransaction(String transaction) throws SVNException { + log.debug("get svn modifications from transaction: {}", transaction); + final Modifications modifications = new Modifications(); + SVNLookClient client = SVNClientManager.newInstance().getLookClient(); + client.doGetChanged(context.getDirectory(), transaction, + e -> SvnUtil.appendModification(modifications, e.getType(), e.getPath()), true); + + return modifications; + } + @Override public Modifications getModifications(ModificationsCommandRequest request) { return getModifications(request.getRevision()); } - } From 1dfbcc336f8ac9b9989446997475ed3fc99e8f8e Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Mon, 4 Feb 2019 11:59:47 +0000 Subject: [PATCH 077/112] Close branch bugfix/get_modifications_for_svn From accd20538e7593ecef11f8c5683c23eed8513e4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= <rene.pfeuffer@cloudogu.com> Date: Mon, 4 Feb 2019 14:35:51 +0100 Subject: [PATCH 078/112] Fix class loader for creation of vcs version string Use UberClassLoader in AbstractSimpleRepositoryHandler#getStringFromResource Therefore pass through plugin loader --- .../scm/repository/AbstractSimpleRepositoryHandler.java | 8 ++++++-- .../java/sonia/scm/repository/GitRepositoryHandler.java | 6 ++++-- .../sonia/scm/repository/GitRepositoryHandlerTest.java | 4 ++-- .../java/sonia/scm/repository/HgRepositoryHandler.java | 6 ++++-- .../sonia/scm/repository/HgRepositoryHandlerTest.java | 4 ++-- .../src/test/java/sonia/scm/repository/HgTestUtil.java | 2 +- .../java/sonia/scm/repository/SvnRepositoryHandler.java | 7 ++++--- .../sonia/scm/repository/SvnRepositoryHandlerTest.java | 8 ++------ .../java/sonia/scm/repository/DummyRepositoryHandler.java | 2 +- 9 files changed, 26 insertions(+), 21 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java index 9941a4253b..663d053ca5 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java @@ -40,6 +40,7 @@ import org.slf4j.LoggerFactory; import sonia.scm.ConfigurationException; import sonia.scm.io.CommandResult; import sonia.scm.io.ExtendedCommand; +import sonia.scm.plugin.PluginLoader; import sonia.scm.store.ConfigurationStoreFactory; import java.io.File; @@ -67,11 +68,14 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig LoggerFactory.getLogger(AbstractSimpleRepositoryHandler.class); private final RepositoryLocationResolver repositoryLocationResolver; + private final PluginLoader pluginLoader; public AbstractSimpleRepositoryHandler(ConfigurationStoreFactory storeFactory, - RepositoryLocationResolver repositoryLocationResolver) { + RepositoryLocationResolver repositoryLocationResolver, + PluginLoader pluginLoader) { super(storeFactory); this.repositoryLocationResolver = repositoryLocationResolver; + this.pluginLoader = pluginLoader; } @Override @@ -155,7 +159,7 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig String content = defaultContent; try { - URL url = Resources.getResource(resource); + URL url = pluginLoader.getUberClassLoader().getResource(resource); if (url != null) { content = Resources.toString(url, Charsets.UTF_8); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryHandler.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryHandler.java index 95225c9e30..63800e8a02 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryHandler.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryHandler.java @@ -44,6 +44,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.SCMContextProvider; import sonia.scm.plugin.Extension; +import sonia.scm.plugin.PluginLoader; import sonia.scm.repository.spi.GitRepositoryServiceProvider; import sonia.scm.schedule.Scheduler; import sonia.scm.schedule.Task; @@ -103,9 +104,10 @@ public class GitRepositoryHandler public GitRepositoryHandler(ConfigurationStoreFactory storeFactory, Scheduler scheduler, RepositoryLocationResolver repositoryLocationResolver, - GitWorkdirFactory workdirFactory) + GitWorkdirFactory workdirFactory, + PluginLoader pluginLoader) { - super(storeFactory, repositoryLocationResolver); + super(storeFactory, repositoryLocationResolver, pluginLoader); this.scheduler = scheduler; this.workdirFactory = workdirFactory; } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java index cb10e15271..66ec320067 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java @@ -94,7 +94,7 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { RepositoryLocationResolver locationResolver, File directory) { GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory, - scheduler, locationResolver, gitWorkdirFactory); + scheduler, locationResolver, gitWorkdirFactory, null); repositoryHandler.init(contextProvider); GitConfig config = new GitConfig(); @@ -108,7 +108,7 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Test public void getDirectory() { GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory, - scheduler, locationResolver, gitWorkdirFactory); + scheduler, locationResolver, gitWorkdirFactory, null); GitConfig config = new GitConfig(); config.setDisabled(false); config.setGcExpression("gc exp"); diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java index c2c0439fc1..7db9a8becb 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java @@ -51,6 +51,7 @@ import sonia.scm.io.INIConfigurationReader; import sonia.scm.io.INIConfigurationWriter; import sonia.scm.io.INISection; import sonia.scm.plugin.Extension; +import sonia.scm.plugin.PluginLoader; import sonia.scm.repository.spi.HgRepositoryServiceProvider; import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.util.IOUtil; @@ -111,9 +112,10 @@ public class HgRepositoryHandler @Inject public HgRepositoryHandler(ConfigurationStoreFactory storeFactory, Provider<HgContext> hgContextProvider, - RepositoryLocationResolver repositoryLocationResolver) + RepositoryLocationResolver repositoryLocationResolver, + PluginLoader pluginLoader) { - super(storeFactory, repositoryLocationResolver); + super(storeFactory, repositoryLocationResolver, pluginLoader); this.hgContextProvider = hgContextProvider; try diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java index c45d9ab358..ed222f5119 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java @@ -77,7 +77,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Override protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver, File directory) { - HgRepositoryHandler handler = new HgRepositoryHandler(factory, new HgContextProvider(), locationResolver); + HgRepositoryHandler handler = new HgRepositoryHandler(factory, new HgContextProvider(), locationResolver, null); handler.init(contextProvider); HgTestUtil.checkForSkip(handler); @@ -87,7 +87,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Test public void getDirectory() { - HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, provider, locationResolver); + HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, provider, locationResolver, null); HgConfig hgConfig = new HgConfig(); hgConfig.setHgBinary("hg"); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java index 68f7e18a76..131dad0837 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java @@ -105,7 +105,7 @@ public final class HgTestUtil RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(context, repoDao, new InitialRepositoryLocationResolver()); HgRepositoryHandler handler = - new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver); + new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver, null); Path repoDir = directory.toPath(); when(repoDao.getPath(any())).thenReturn(repoDir); handler.init(context); diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java index 86f99cd517..639a16968c 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java @@ -53,9 +53,9 @@ import sonia.scm.io.INIConfigurationWriter; import sonia.scm.io.INISection; import sonia.scm.logging.SVNKitLogger; import sonia.scm.plugin.Extension; +import sonia.scm.plugin.PluginLoader; import sonia.scm.repository.spi.HookEventFacade; import sonia.scm.repository.spi.SvnRepositoryServiceProvider; -import sonia.scm.store.ConfigurationStore; import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.util.Util; @@ -97,9 +97,10 @@ public class SvnRepositoryHandler @Inject public SvnRepositoryHandler(ConfigurationStoreFactory storeFactory, HookEventFacade eventFacade, - RepositoryLocationResolver repositoryLocationResolver) + RepositoryLocationResolver repositoryLocationResolver, + PluginLoader pluginLoader) { - super(storeFactory, repositoryLocationResolver); + super(storeFactory, repositoryLocationResolver, pluginLoader); // register logger SVNDebugLog.setDefaultLog(new SVNKitLogger()); diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java index 7b22e15c94..fc0a9e9ae0 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java @@ -32,14 +32,10 @@ package sonia.scm.repository; -import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.api.HookContextFactory; import sonia.scm.repository.spi.HookEventFacade; -import sonia.scm.store.ConfigurationStore; import sonia.scm.store.ConfigurationStoreFactory; import java.io.File; @@ -93,7 +89,7 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver, File directory) { - SvnRepositoryHandler handler = new SvnRepositoryHandler(factory, null, locationResolver); + SvnRepositoryHandler handler = new SvnRepositoryHandler(factory, null, locationResolver, null); handler.init(contextProvider); @@ -109,7 +105,7 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { public void getDirectory() { when(factory.withType(any())).thenCallRealMethod(); SvnRepositoryHandler repositoryHandler = new SvnRepositoryHandler(factory, - facade, locationResolver); + facade, locationResolver, null); SvnConfig svnConfig = new SvnConfig(); repositoryHandler.setConfig(svnConfig); diff --git a/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java b/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java index 3efe78c820..ccaeee8631 100644 --- a/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java +++ b/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java @@ -59,7 +59,7 @@ public class DummyRepositoryHandler private final Set<String> existingRepoNames = new HashSet<>(); public DummyRepositoryHandler(ConfigurationStoreFactory storeFactory, RepositoryLocationResolver repositoryLocationResolver) { - super(storeFactory, repositoryLocationResolver); + super(storeFactory, repositoryLocationResolver, null); } @Override From a180f9b79526052efcc194f201755900625e826e Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Mon, 4 Feb 2019 14:39:29 +0100 Subject: [PATCH 079/112] rename LinkEnricher to HalEnricher --- .../scm/api/v2/resources/BaseMapper.java | 2 +- .../{LinkAppender.java => HalAppender.java} | 4 +- ...nderMapper.java => HalAppenderMapper.java} | 14 ++-- .../{LinkEnricher.java => HalEnricher.java} | 12 ++-- ...erContext.java => HalEnricherContext.java} | 12 ++-- ...Registry.java => HalEnricherRegistry.java} | 14 ++-- .../sonia/scm/api/v2/resources/Index.java | 2 +- .../java/sonia/scm/api/v2/resources/Me.java | 2 +- ...erTest.java => HalAppenderMapperTest.java} | 12 ++-- ...tTest.java => HalEnricherContextTest.java} | 12 ++-- ...Test.java => HalEnricherRegistryTest.java} | 26 ++++---- .../v2/resources/BranchToBranchDtoMapper.java | 4 +- .../ChangesetToChangesetDtoMapper.java | 4 +- ...nkAppender.java => EdisonHalAppender.java} | 4 +- .../FileObjectToFileObjectDtoMapper.java | 7 +- .../v2/resources/GroupToGroupDtoMapper.java | 2 +- .../api/v2/resources/IndexDtoGenerator.java | 4 +- .../LinkEnricherAutoRegistration.java | 12 ++-- .../scm/api/v2/resources/MeDtoFactory.java | 4 +- .../RepositoryToRepositoryDtoMapper.java | 2 +- .../api/v2/resources/TagToTagDtoMapper.java | 4 +- .../api/v2/resources/UserToUserDtoMapper.java | 2 +- .../BranchToBranchDtoMapperTest.java | 2 +- ...erTest.java => EdisonHalAppenderTest.java} | 6 +- .../FileObjectToFileObjectDtoMapperTest.java | 2 +- .../resources/GroupToGroupDtoMapperTest.java | 3 +- .../HalEnricherAutoRegistrationTest.java | 64 +++++++++++++++++++ .../LinkEnricherAutoRegistrationTest.java | 64 ------------------- .../api/v2/resources/MeDtoFactoryTest.java | 3 +- .../RepositoryToRepositoryDtoMapperTest.java | 2 +- .../v2/resources/TagToTagDtoMapperTest.java | 2 +- .../v2/resources/UserToUserDtoMapperTest.java | 2 +- 32 files changed, 153 insertions(+), 158 deletions(-) rename scm-core/src/main/java/sonia/scm/api/v2/resources/{LinkAppender.java => HalAppender.java} (85%) rename scm-core/src/main/java/sonia/scm/api/v2/resources/{LinkAppenderMapper.java => HalAppenderMapper.java} (56%) rename scm-core/src/main/java/sonia/scm/api/v2/resources/{LinkEnricher.java => HalEnricher.java} (51%) rename scm-core/src/main/java/sonia/scm/api/v2/resources/{LinkEnricherContext.java => HalEnricherContext.java} (80%) rename scm-core/src/main/java/sonia/scm/api/v2/resources/{LinkEnricherRegistry.java => HalEnricherRegistry.java} (53%) rename scm-core/src/test/java/sonia/scm/api/v2/resources/{LinkAppenderMapperTest.java => HalAppenderMapperTest.java} (89%) rename scm-core/src/test/java/sonia/scm/api/v2/resources/{LinkEnricherContextTest.java => HalEnricherContextTest.java} (72%) rename scm-core/src/test/java/sonia/scm/api/v2/resources/{LinkEnricherRegistryTest.java => HalEnricherRegistryTest.java} (53%) rename scm-webapp/src/main/java/sonia/scm/api/v2/resources/{EdisonLinkAppender.java => EdisonHalAppender.java} (91%) rename scm-webapp/src/test/java/sonia/scm/api/v2/resources/{EdisonLinkAppenderTest.java => EdisonHalAppenderTest.java} (88%) create mode 100644 scm-webapp/src/test/java/sonia/scm/api/v2/resources/HalEnricherAutoRegistrationTest.java delete mode 100644 scm-webapp/src/test/java/sonia/scm/api/v2/resources/LinkEnricherAutoRegistrationTest.java diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/BaseMapper.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/BaseMapper.java index 89cc893131..eba8173de1 100644 --- a/scm-core/src/main/java/sonia/scm/api/v2/resources/BaseMapper.java +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/BaseMapper.java @@ -3,7 +3,7 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.HalRepresentation; import org.mapstruct.Mapping; -public abstract class BaseMapper<T, D extends HalRepresentation> extends LinkAppenderMapper implements InstantAttributeMapper { +public abstract class BaseMapper<T, D extends HalRepresentation> extends HalAppenderMapper implements InstantAttributeMapper { @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes public abstract D map(T modelObject); diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/LinkAppender.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/HalAppender.java similarity index 85% rename from scm-core/src/main/java/sonia/scm/api/v2/resources/LinkAppender.java rename to scm-core/src/main/java/sonia/scm/api/v2/resources/HalAppender.java index d3864dc798..14cd5153e6 100644 --- a/scm-core/src/main/java/sonia/scm/api/v2/resources/LinkAppender.java +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/HalAppender.java @@ -1,12 +1,12 @@ package sonia.scm.api.v2.resources; /** - * The {@link LinkAppender} can be used within an {@link LinkEnricher} to append hateoas links to a json response. + * The {@link HalAppender} can be used within an {@link HalEnricher} to append hateoas links to a json response. * * @author Sebastian Sdorra * @since 2.0.0 */ -public interface LinkAppender { +public interface HalAppender { /** * Appends one link to the json response. diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/LinkAppenderMapper.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/HalAppenderMapper.java similarity index 56% rename from scm-core/src/main/java/sonia/scm/api/v2/resources/LinkAppenderMapper.java rename to scm-core/src/main/java/sonia/scm/api/v2/resources/HalAppenderMapper.java index 7843491b71..04310efad2 100644 --- a/scm-core/src/main/java/sonia/scm/api/v2/resources/LinkAppenderMapper.java +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/HalAppenderMapper.java @@ -4,17 +4,17 @@ import com.google.common.annotations.VisibleForTesting; import javax.inject.Inject; -public class LinkAppenderMapper { +public class HalAppenderMapper { @Inject - private LinkEnricherRegistry registry; + private HalEnricherRegistry registry; @VisibleForTesting - void setRegistry(LinkEnricherRegistry registry) { + void setRegistry(HalEnricherRegistry registry) { this.registry = registry; } - protected void appendLinks(LinkAppender appender, Object source, Object... contextEntries) { + protected void appendLinks(HalAppender appender, Object source, Object... contextEntries) { // null check is only their to not break existing tests if (registry != null) { @@ -24,10 +24,10 @@ public class LinkAppenderMapper { ctx[i + 1] = contextEntries[i]; } - LinkEnricherContext context = LinkEnricherContext.of(ctx); + HalEnricherContext context = HalEnricherContext.of(ctx); - Iterable<LinkEnricher> enrichers = registry.allByType(source.getClass()); - for (LinkEnricher enricher : enrichers) { + Iterable<HalEnricher> enrichers = registry.allByType(source.getClass()); + for (HalEnricher enricher : enrichers) { enricher.enrich(context, appender); } } diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/LinkEnricher.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/HalEnricher.java similarity index 51% rename from scm-core/src/main/java/sonia/scm/api/v2/resources/LinkEnricher.java rename to scm-core/src/main/java/sonia/scm/api/v2/resources/HalEnricher.java index c16d6f6482..647a1cf74e 100644 --- a/scm-core/src/main/java/sonia/scm/api/v2/resources/LinkEnricher.java +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/HalEnricher.java @@ -3,8 +3,8 @@ package sonia.scm.api.v2.resources; import sonia.scm.plugin.ExtensionPoint; /** - * A {@link LinkEnricher} can be used to append hateoas links to a specific json response. - * To register an enricher use the {@link Enrich} annotation or the {@link LinkEnricherRegistry} which is available + * A {@link HalEnricher} can be used to append hal specific attributes, such as links, to the json response. + * To register an enricher use the {@link Enrich} annotation or the {@link HalEnricherRegistry} which is available * via injection. * * <b>Warning:</b> enrichers are always registered as singletons. @@ -14,13 +14,13 @@ import sonia.scm.plugin.ExtensionPoint; */ @ExtensionPoint @FunctionalInterface -public interface LinkEnricher { +public interface HalEnricher { /** - * Enriches the response with hateoas links. + * Enriches the response with hal specific attributes. * * @param context contains the source for the json mapping and related objects - * @param appender can be used to append links to the json response + * @param appender can be used to append links or embedded objects to the json response */ - void enrich(LinkEnricherContext context, LinkAppender appender); + void enrich(HalEnricherContext context, HalAppender appender); } diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/LinkEnricherContext.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/HalEnricherContext.java similarity index 80% rename from scm-core/src/main/java/sonia/scm/api/v2/resources/LinkEnricherContext.java rename to scm-core/src/main/java/sonia/scm/api/v2/resources/HalEnricherContext.java index 2808a923e9..36128087b8 100644 --- a/scm-core/src/main/java/sonia/scm/api/v2/resources/LinkEnricherContext.java +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/HalEnricherContext.java @@ -7,17 +7,17 @@ import java.util.NoSuchElementException; import java.util.Optional; /** - * Context object for the {@link LinkEnricher}. The context holds the source object for the json and all related - * objects, which can be useful for the link creation. + * Context object for the {@link HalEnricher}. The context holds the source object for the json and all related + * objects, which can be useful for the enrichment. * * @author Sebastian Sdorra * @since 2.0.0 */ -public final class LinkEnricherContext { +public final class HalEnricherContext { private final Map<Class, Object> instanceMap; - private LinkEnricherContext(Map<Class,Object> instanceMap) { + private HalEnricherContext(Map<Class,Object> instanceMap) { this.instanceMap = instanceMap; } @@ -28,12 +28,12 @@ public final class LinkEnricherContext { * * @return context of given entries */ - public static LinkEnricherContext of(Object... instances) { + public static HalEnricherContext of(Object... instances) { ImmutableMap.Builder<Class, Object> builder = ImmutableMap.builder(); for (Object instance : instances) { builder.put(instance.getClass(), instance); } - return new LinkEnricherContext(builder.build()); + return new HalEnricherContext(builder.build()); } /** diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/LinkEnricherRegistry.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/HalEnricherRegistry.java similarity index 53% rename from scm-core/src/main/java/sonia/scm/api/v2/resources/LinkEnricherRegistry.java rename to scm-core/src/main/java/sonia/scm/api/v2/resources/HalEnricherRegistry.java index cd95a62ec3..3fadbfa388 100644 --- a/scm-core/src/main/java/sonia/scm/api/v2/resources/LinkEnricherRegistry.java +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/HalEnricherRegistry.java @@ -7,34 +7,34 @@ import sonia.scm.plugin.Extension; import javax.inject.Singleton; /** - * The {@link LinkEnricherRegistry} is responsible for binding {@link LinkEnricher} instances to their source types. + * The {@link HalEnricherRegistry} is responsible for binding {@link HalEnricher} instances to their source types. * * @author Sebastian Sdorra * @since 2.0.0 */ @Extension @Singleton -public final class LinkEnricherRegistry { +public final class HalEnricherRegistry { - private final Multimap<Class, LinkEnricher> enrichers = HashMultimap.create(); + private final Multimap<Class, HalEnricher> enrichers = HashMultimap.create(); /** - * Registers a new {@link LinkEnricher} for the given source type. + * Registers a new {@link HalEnricher} for the given source type. * * @param sourceType type of json mapping source * @param enricher link enricher instance */ - public void register(Class sourceType, LinkEnricher enricher) { + public void register(Class sourceType, HalEnricher enricher) { enrichers.put(sourceType, enricher); } /** - * Returns all registered {@link LinkEnricher} for the given type. + * Returns all registered {@link HalEnricher} for the given type. * * @param sourceType type of json mapping source * @return all registered enrichers */ - public Iterable<LinkEnricher> allByType(Class sourceType) { + public Iterable<HalEnricher> allByType(Class sourceType) { return enrichers.get(sourceType); } } diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/Index.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/Index.java index bf20f26a7a..346ce83816 100644 --- a/scm-core/src/main/java/sonia/scm/api/v2/resources/Index.java +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/Index.java @@ -1,7 +1,7 @@ package sonia.scm.api.v2.resources; /** - * The {@link Index} object can be used to register a {@link LinkEnricher} for the index resource. + * The {@link Index} object can be used to register a {@link HalEnricher} for the index resource. * * @author Sebastian Sdorra * @since 2.0.0 diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/Me.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/Me.java index f8f82804a6..a027a78d79 100644 --- a/scm-core/src/main/java/sonia/scm/api/v2/resources/Me.java +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/Me.java @@ -1,7 +1,7 @@ package sonia.scm.api.v2.resources; /** - * The {@link Me} object can be used to register a {@link LinkEnricher} for the me resource. + * The {@link Me} object can be used to register a {@link HalEnricher} for the me resource. * * @author Sebastian Sdorra * @since 2.0.0 diff --git a/scm-core/src/test/java/sonia/scm/api/v2/resources/LinkAppenderMapperTest.java b/scm-core/src/test/java/sonia/scm/api/v2/resources/HalAppenderMapperTest.java similarity index 89% rename from scm-core/src/test/java/sonia/scm/api/v2/resources/LinkAppenderMapperTest.java rename to scm-core/src/test/java/sonia/scm/api/v2/resources/HalAppenderMapperTest.java index 557eac2020..639671f423 100644 --- a/scm-core/src/test/java/sonia/scm/api/v2/resources/LinkAppenderMapperTest.java +++ b/scm-core/src/test/java/sonia/scm/api/v2/resources/HalAppenderMapperTest.java @@ -11,18 +11,18 @@ import java.util.Optional; import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) -class LinkAppenderMapperTest { +class HalAppenderMapperTest { @Mock - private LinkAppender appender; + private HalAppender appender; - private LinkEnricherRegistry registry; - private LinkAppenderMapper mapper; + private HalEnricherRegistry registry; + private HalAppenderMapper mapper; @BeforeEach void beforeEach() { - registry = new LinkEnricherRegistry(); - mapper = new LinkAppenderMapper(); + registry = new HalEnricherRegistry(); + mapper = new HalAppenderMapper(); mapper.setRegistry(registry); } diff --git a/scm-core/src/test/java/sonia/scm/api/v2/resources/LinkEnricherContextTest.java b/scm-core/src/test/java/sonia/scm/api/v2/resources/HalEnricherContextTest.java similarity index 72% rename from scm-core/src/test/java/sonia/scm/api/v2/resources/LinkEnricherContextTest.java rename to scm-core/src/test/java/sonia/scm/api/v2/resources/HalEnricherContextTest.java index 6eb7bb4c84..1aecb5ad46 100644 --- a/scm-core/src/test/java/sonia/scm/api/v2/resources/LinkEnricherContextTest.java +++ b/scm-core/src/test/java/sonia/scm/api/v2/resources/HalEnricherContextTest.java @@ -7,17 +7,17 @@ import org.junit.jupiter.api.Test; import java.util.NoSuchElementException; -class LinkEnricherContextTest { +class HalEnricherContextTest { @Test void shouldCreateContextFromSingleObject() { - LinkEnricherContext context = LinkEnricherContext.of("hello"); + HalEnricherContext context = HalEnricherContext.of("hello"); assertThat(context.oneByType(String.class)).contains("hello"); } @Test void shouldCreateContextFromMultipleObjects() { - LinkEnricherContext context = LinkEnricherContext.of("hello", Integer.valueOf(42), Long.valueOf(21L)); + HalEnricherContext context = HalEnricherContext.of("hello", Integer.valueOf(42), Long.valueOf(21L)); assertThat(context.oneByType(String.class)).contains("hello"); assertThat(context.oneByType(Integer.class)).contains(42); assertThat(context.oneByType(Long.class)).contains(21L); @@ -25,19 +25,19 @@ class LinkEnricherContextTest { @Test void shouldReturnEmptyOptionalForUnknownTypes() { - LinkEnricherContext context = LinkEnricherContext.of(); + HalEnricherContext context = HalEnricherContext.of(); assertThat(context.oneByType(String.class)).isNotPresent(); } @Test void shouldReturnRequiredObject() { - LinkEnricherContext context = LinkEnricherContext.of("hello"); + HalEnricherContext context = HalEnricherContext.of("hello"); assertThat(context.oneRequireByType(String.class)).isEqualTo("hello"); } @Test void shouldThrowAnNoSuchElementExceptionForUnknownTypes() { - LinkEnricherContext context = LinkEnricherContext.of(); + HalEnricherContext context = HalEnricherContext.of(); assertThrows(NoSuchElementException.class, () -> context.oneRequireByType(String.class)); } diff --git a/scm-core/src/test/java/sonia/scm/api/v2/resources/LinkEnricherRegistryTest.java b/scm-core/src/test/java/sonia/scm/api/v2/resources/HalEnricherRegistryTest.java similarity index 53% rename from scm-core/src/test/java/sonia/scm/api/v2/resources/LinkEnricherRegistryTest.java rename to scm-core/src/test/java/sonia/scm/api/v2/resources/HalEnricherRegistryTest.java index 07441003d7..6a863d2f04 100644 --- a/scm-core/src/test/java/sonia/scm/api/v2/resources/LinkEnricherRegistryTest.java +++ b/scm-core/src/test/java/sonia/scm/api/v2/resources/HalEnricherRegistryTest.java @@ -5,54 +5,54 @@ import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; -class LinkEnricherRegistryTest { +class HalEnricherRegistryTest { - private LinkEnricherRegistry registry; + private HalEnricherRegistry registry; @BeforeEach void setUpObjectUnderTest() { - registry = new LinkEnricherRegistry(); + registry = new HalEnricherRegistry(); } @Test void shouldRegisterTheEnricher() { - SampleLinkEnricher enricher = new SampleLinkEnricher(); + SampleHalEnricher enricher = new SampleHalEnricher(); registry.register(String.class, enricher); - Iterable<LinkEnricher> enrichers = registry.allByType(String.class); + Iterable<HalEnricher> enrichers = registry.allByType(String.class); assertThat(enrichers).containsOnly(enricher); } @Test void shouldRegisterMultipleEnrichers() { - SampleLinkEnricher one = new SampleLinkEnricher(); + SampleHalEnricher one = new SampleHalEnricher(); registry.register(String.class, one); - SampleLinkEnricher two = new SampleLinkEnricher(); + SampleHalEnricher two = new SampleHalEnricher(); registry.register(String.class, two); - Iterable<LinkEnricher> enrichers = registry.allByType(String.class); + Iterable<HalEnricher> enrichers = registry.allByType(String.class); assertThat(enrichers).containsOnly(one, two); } @Test void shouldRegisterEnrichersForDifferentTypes() { - SampleLinkEnricher one = new SampleLinkEnricher(); + SampleHalEnricher one = new SampleHalEnricher(); registry.register(String.class, one); - SampleLinkEnricher two = new SampleLinkEnricher(); + SampleHalEnricher two = new SampleHalEnricher(); registry.register(Integer.class, two); - Iterable<LinkEnricher> enrichers = registry.allByType(String.class); + Iterable<HalEnricher> enrichers = registry.allByType(String.class); assertThat(enrichers).containsOnly(one); enrichers = registry.allByType(Integer.class); assertThat(enrichers).containsOnly(two); } - private static class SampleLinkEnricher implements LinkEnricher { + private static class SampleHalEnricher implements HalEnricher { @Override - public void enrich(LinkEnricherContext context, LinkAppender appender) { + public void enrich(HalEnricherContext context, HalAppender appender) { } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapper.java index 7e6f0c074c..0fdd16fa56 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapper.java @@ -15,7 +15,7 @@ import static de.otto.edison.hal.Link.linkBuilder; import static de.otto.edison.hal.Links.linkingTo; @Mapper -public abstract class BranchToBranchDtoMapper extends LinkAppenderMapper { +public abstract class BranchToBranchDtoMapper extends HalAppenderMapper { @Inject private ResourceLinks resourceLinks; @@ -31,7 +31,7 @@ public abstract class BranchToBranchDtoMapper extends LinkAppenderMapper { .single(linkBuilder("changeset", resourceLinks.changeset().changeset(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())).build()) .single(linkBuilder("source", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())).build()); - appendLinks(new EdisonLinkAppender(linksBuilder), source, namespaceAndName); + appendLinks(new EdisonHalAppender(linksBuilder), source, namespaceAndName); target.add(linksBuilder.build()); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java index 219062d320..25bd0ef661 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java @@ -23,7 +23,7 @@ import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Links.linkingTo; @Mapper -public abstract class ChangesetToChangesetDtoMapper extends LinkAppenderMapper implements InstantAttributeMapper { +public abstract class ChangesetToChangesetDtoMapper extends HalAppenderMapper implements InstantAttributeMapper { @Inject private RepositoryServiceFactory serviceFactory; @@ -68,7 +68,7 @@ public abstract class ChangesetToChangesetDtoMapper extends LinkAppenderMapper i .single(link("diff", resourceLinks.diff().self(namespace, name, target.getId()))) .single(link("modifications", resourceLinks.modifications().self(namespace, name, target.getId()))); - appendLinks(new EdisonLinkAppender(linksBuilder), source, repository); + appendLinks(new EdisonHalAppender(linksBuilder), source, repository); target.add(linksBuilder.build()); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/EdisonLinkAppender.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/EdisonHalAppender.java similarity index 91% rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/EdisonLinkAppender.java rename to scm-webapp/src/main/java/sonia/scm/api/v2/resources/EdisonHalAppender.java index c4e699cb58..14db542976 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/EdisonLinkAppender.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/EdisonHalAppender.java @@ -6,11 +6,11 @@ import de.otto.edison.hal.Links; import java.util.ArrayList; import java.util.List; -class EdisonLinkAppender implements LinkAppender { +class EdisonHalAppender implements HalAppender { private final Links.Builder builder; - EdisonLinkAppender(Links.Builder builder) { + EdisonHalAppender(Links.Builder builder) { this.builder = builder; } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java index 2432d5168c..132abce71b 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java @@ -12,13 +12,10 @@ import sonia.scm.repository.SubRepository; import javax.inject.Inject; -import java.util.List; -import java.util.stream.Collectors; - import static de.otto.edison.hal.Link.link; @Mapper -public abstract class FileObjectToFileObjectDtoMapper extends LinkAppenderMapper implements InstantAttributeMapper { +public abstract class FileObjectToFileObjectDtoMapper extends HalAppenderMapper implements InstantAttributeMapper { @Inject private ResourceLinks resourceLinks; @@ -39,7 +36,7 @@ public abstract class FileObjectToFileObjectDtoMapper extends LinkAppenderMapper links.single(link("history", resourceLinks.fileHistory().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, path))); } - appendLinks(new EdisonLinkAppender(links), fileObject, namespaceAndName, revision); + appendLinks(new EdisonHalAppender(links), fileObject, namespaceAndName, revision); dto.add(links.build()); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupToGroupDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupToGroupDtoMapper.java index bf866af350..a728632395 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupToGroupDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupToGroupDtoMapper.java @@ -36,7 +36,7 @@ public abstract class GroupToGroupDtoMapper extends BaseMapper<Group, GroupDto> linksBuilder.single(link("permissions", resourceLinks.groupPermissions().permissions(target.getName()))); } - appendLinks(new EdisonLinkAppender(linksBuilder), group); + appendLinks(new EdisonHalAppender(linksBuilder), group); target.add(linksBuilder.build()); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java index 3eff661385..ed848c4311 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java @@ -15,7 +15,7 @@ import java.util.List; import static de.otto.edison.hal.Link.link; -public class IndexDtoGenerator extends LinkAppenderMapper { +public class IndexDtoGenerator extends HalAppenderMapper { private final ResourceLinks resourceLinks; private final SCMContextProvider scmContextProvider; @@ -61,7 +61,7 @@ public class IndexDtoGenerator extends LinkAppenderMapper { builder.single(link("login", resourceLinks.authentication().jsonLogin())); } - appendLinks(new EdisonLinkAppender(builder), new Index()); + appendLinks(new EdisonHalAppender(builder), new Index()); return new IndexDto(scmContextProvider.getVersion(), builder.build()); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/LinkEnricherAutoRegistration.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/LinkEnricherAutoRegistration.java index 890e268ed5..8472eb9fc1 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/LinkEnricherAutoRegistration.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/LinkEnricherAutoRegistration.java @@ -10,30 +10,30 @@ import javax.servlet.ServletContextListener; import java.util.Set; /** - * Registers every {@link LinkEnricher} which is annotated with an {@link Enrich} annotation. + * Registers every {@link HalEnricher} which is annotated with an {@link Enrich} annotation. */ @Extension public class LinkEnricherAutoRegistration implements ServletContextListener { private static final Logger LOG = LoggerFactory.getLogger(LinkEnricherAutoRegistration.class); - private final LinkEnricherRegistry registry; - private final Set<LinkEnricher> enrichers; + private final HalEnricherRegistry registry; + private final Set<HalEnricher> enrichers; @Inject - public LinkEnricherAutoRegistration(LinkEnricherRegistry registry, Set<LinkEnricher> enrichers) { + public LinkEnricherAutoRegistration(HalEnricherRegistry registry, Set<HalEnricher> enrichers) { this.registry = registry; this.enrichers = enrichers; } @Override public void contextInitialized(ServletContextEvent sce) { - for (LinkEnricher enricher : enrichers) { + for (HalEnricher enricher : enrichers) { Enrich annotation = enricher.getClass().getAnnotation(Enrich.class); if (annotation != null) { registry.register(annotation.value(), enricher); } else { - LOG.warn("found LinkEnricher extension {} without Enrich annotation", enricher.getClass()); + LOG.warn("found HalEnricher extension {} without Enrich annotation", enricher.getClass()); } } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeDtoFactory.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeDtoFactory.java index 082db7fd94..8763c54565 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeDtoFactory.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeDtoFactory.java @@ -16,7 +16,7 @@ import java.util.Collections; import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Links.linkingTo; -public class MeDtoFactory extends LinkAppenderMapper { +public class MeDtoFactory extends HalAppenderMapper { private final ResourceLinks resourceLinks; private final UserManager userManager; @@ -73,7 +73,7 @@ public class MeDtoFactory extends LinkAppenderMapper { linksBuilder.single(link("password", resourceLinks.me().passwordChange())); } - appendLinks(new EdisonLinkAppender(linksBuilder), new Me(), user); + appendLinks(new EdisonHalAppender(linksBuilder), new Me(), user); target.add(linksBuilder.build()); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java index 19929b63ba..de03f1aa05 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java @@ -70,7 +70,7 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit linksBuilder.single(link("changesets", resourceLinks.changeset().all(target.getNamespace(), target.getName()))); linksBuilder.single(link("sources", resourceLinks.source().selfWithoutRevision(target.getNamespace(), target.getName()))); - appendLinks(new EdisonLinkAppender(linksBuilder), repository); + appendLinks(new EdisonHalAppender(linksBuilder), repository); target.add(linksBuilder.build()); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java index 5ede1cb55b..4e0518a006 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java @@ -15,7 +15,7 @@ import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Links.linkingTo; @Mapper -public abstract class TagToTagDtoMapper extends LinkAppenderMapper { +public abstract class TagToTagDtoMapper extends HalAppenderMapper { @Inject private ResourceLinks resourceLinks; @@ -30,7 +30,7 @@ public abstract class TagToTagDtoMapper extends LinkAppenderMapper { .single(link("sources", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision()))) .single(link("changeset", resourceLinks.changeset().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision()))); - appendLinks(new EdisonLinkAppender(linksBuilder), tag, namespaceAndName); + appendLinks(new EdisonHalAppender(linksBuilder), tag, namespaceAndName); target.add(linksBuilder.build()); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserToUserDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserToUserDtoMapper.java index 3c7e9fd7f1..2465293356 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserToUserDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserToUserDtoMapper.java @@ -47,7 +47,7 @@ public abstract class UserToUserDtoMapper extends BaseMapper<User, UserDto> { linksBuilder.single(link("permissions", resourceLinks.userPermissions().permissions(target.getName()))); } - appendLinks(new EdisonLinkAppender(linksBuilder), user); + appendLinks(new EdisonHalAppender(linksBuilder), user); target.add(linksBuilder.build()); } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapperTest.java index d2e202576a..7b19603218 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapperTest.java @@ -24,7 +24,7 @@ class BranchToBranchDtoMapperTest { @Test void shouldAppendLinks() { - LinkEnricherRegistry registry = new LinkEnricherRegistry(); + HalEnricherRegistry registry = new HalEnricherRegistry(); registry.register(Branch.class, (ctx, appender) -> { NamespaceAndName namespaceAndName = ctx.oneRequireByType(NamespaceAndName.class); Branch branch = ctx.oneRequireByType(Branch.class); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/EdisonLinkAppenderTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/EdisonHalAppenderTest.java similarity index 88% rename from scm-webapp/src/test/java/sonia/scm/api/v2/resources/EdisonLinkAppenderTest.java rename to scm-webapp/src/test/java/sonia/scm/api/v2/resources/EdisonHalAppenderTest.java index e97415cc09..8b326b9f05 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/EdisonLinkAppenderTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/EdisonHalAppenderTest.java @@ -10,15 +10,15 @@ import java.util.List; import static de.otto.edison.hal.Links.linkingTo; import static org.assertj.core.api.Assertions.assertThat; -class EdisonLinkAppenderTest { +class EdisonHalAppenderTest { private Links.Builder builder; - private EdisonLinkAppender appender; + private EdisonHalAppender appender; @BeforeEach void prepare() { builder = linkingTo(); - appender = new EdisonLinkAppender(builder); + appender = new EdisonHalAppender(builder); } @Test diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapperTest.java index b25410210f..9f399fe93b 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapperTest.java @@ -73,7 +73,7 @@ public class FileObjectToFileObjectDtoMapperTest { @Test public void shouldAppendLinks() { - LinkEnricherRegistry registry = new LinkEnricherRegistry(); + HalEnricherRegistry registry = new HalEnricherRegistry(); registry.register(FileObject.class, (ctx, appender) -> { NamespaceAndName repository = ctx.oneRequireByType(NamespaceAndName.class); FileObject fo = ctx.oneRequireByType(FileObject.class); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupToGroupDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupToGroupDtoMapperTest.java index b681dff21f..bb1e9144b2 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupToGroupDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupToGroupDtoMapperTest.java @@ -11,7 +11,6 @@ import org.mockito.InjectMocks; import sonia.scm.group.Group; import java.net.URI; -import java.net.URISyntaxException; import java.util.stream.IntStream; import static java.util.stream.Collectors.toList; @@ -91,7 +90,7 @@ public class GroupToGroupDtoMapperTest { @Test public void shouldAppendLinks() { - LinkEnricherRegistry registry = new LinkEnricherRegistry(); + HalEnricherRegistry registry = new HalEnricherRegistry(); registry.register(Group.class, (ctx, appender) -> { Group group = ctx.oneRequireByType(Group.class); appender.appendOne("some", "http://" + group.getName()); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/HalEnricherAutoRegistrationTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/HalEnricherAutoRegistrationTest.java new file mode 100644 index 0000000000..314dcf11c2 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/HalEnricherAutoRegistrationTest.java @@ -0,0 +1,64 @@ +package sonia.scm.api.v2.resources; + +import com.google.common.collect.ImmutableSet; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.assertj.core.api.Java6Assertions.assertThat; + +class HalEnricherAutoRegistrationTest { + + @Test + void shouldRegisterAllAvailableLinkEnrichers() { + HalEnricher one = new One(); + HalEnricher two = new Two(); + HalEnricher three = new Three(); + HalEnricher four = new Four(); + Set<HalEnricher> enrichers = ImmutableSet.of(one, two, three, four); + + HalEnricherRegistry registry = new HalEnricherRegistry(); + + LinkEnricherAutoRegistration autoRegistration = new LinkEnricherAutoRegistration(registry, enrichers); + autoRegistration.contextInitialized(null); + + assertThat(registry.allByType(String.class)).containsOnly(one, two); + assertThat(registry.allByType(Integer.class)).containsOnly(three); + } + + @Enrich(String.class) + public static class One implements HalEnricher { + + @Override + public void enrich(HalEnricherContext context, HalAppender appender) { + + } + } + + @Enrich(String.class) + public static class Two implements HalEnricher { + + @Override + public void enrich(HalEnricherContext context, HalAppender appender) { + + } + } + + @Enrich(Integer.class) + public static class Three implements HalEnricher { + + @Override + public void enrich(HalEnricherContext context, HalAppender appender) { + + } + } + + public static class Four implements HalEnricher { + + @Override + public void enrich(HalEnricherContext context, HalAppender appender) { + + } + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/LinkEnricherAutoRegistrationTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/LinkEnricherAutoRegistrationTest.java deleted file mode 100644 index a2b72abc49..0000000000 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/LinkEnricherAutoRegistrationTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package sonia.scm.api.v2.resources; - -import com.google.common.collect.ImmutableSet; -import org.junit.jupiter.api.Test; - -import java.util.Set; - -import static org.assertj.core.api.Java6Assertions.assertThat; - -class LinkEnricherAutoRegistrationTest { - - @Test - void shouldRegisterAllAvailableLinkEnrichers() { - LinkEnricher one = new One(); - LinkEnricher two = new Two(); - LinkEnricher three = new Three(); - LinkEnricher four = new Four(); - Set<LinkEnricher> enrichers = ImmutableSet.of(one, two, three, four); - - LinkEnricherRegistry registry = new LinkEnricherRegistry(); - - LinkEnricherAutoRegistration autoRegistration = new LinkEnricherAutoRegistration(registry, enrichers); - autoRegistration.contextInitialized(null); - - assertThat(registry.allByType(String.class)).containsOnly(one, two); - assertThat(registry.allByType(Integer.class)).containsOnly(three); - } - - @Enrich(String.class) - public static class One implements LinkEnricher { - - @Override - public void enrich(LinkEnricherContext context, LinkAppender appender) { - - } - } - - @Enrich(String.class) - public static class Two implements LinkEnricher { - - @Override - public void enrich(LinkEnricherContext context, LinkAppender appender) { - - } - } - - @Enrich(Integer.class) - public static class Three implements LinkEnricher { - - @Override - public void enrich(LinkEnricherContext context, LinkAppender appender) { - - } - } - - public static class Four implements LinkEnricher { - - @Override - public void enrich(LinkEnricherContext context, LinkAppender appender) { - - } - } - -} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeDtoFactoryTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeDtoFactoryTest.java index 138387938b..226a09231d 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeDtoFactoryTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeDtoFactoryTest.java @@ -15,7 +15,6 @@ import org.mockito.quality.Strictness; import sonia.scm.group.GroupNames; import sonia.scm.user.User; import sonia.scm.user.UserManager; -import sonia.scm.user.UserPermissions; import sonia.scm.user.UserTestData; import java.net.URI; @@ -170,7 +169,7 @@ class MeDtoFactoryTest { void shouldAppendLinks() { prepareSubject(UserTestData.createTrillian()); - LinkEnricherRegistry registry = new LinkEnricherRegistry(); + HalEnricherRegistry registry = new HalEnricherRegistry(); meDtoFactory.setRegistry(registry); registry.register(Me.class, (ctx, appender) -> { diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java index 8469e966c8..9a478d1ecb 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java @@ -211,7 +211,7 @@ public class RepositoryToRepositoryDtoMapperTest { @Test public void shouldAppendLinks() { - LinkEnricherRegistry registry = new LinkEnricherRegistry(); + HalEnricherRegistry registry = new HalEnricherRegistry(); registry.register(Repository.class, (ctx, appender) -> { Repository repository = ctx.oneRequireByType(Repository.class); appender.appendOne("id", "http://" + repository.getId()); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagToTagDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagToTagDtoMapperTest.java index aa8eb3e7ab..03e726a197 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagToTagDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagToTagDtoMapperTest.java @@ -22,7 +22,7 @@ class TagToTagDtoMapperTest { @Test void shouldAppendLinks() { - LinkEnricherRegistry registry = new LinkEnricherRegistry(); + HalEnricherRegistry registry = new HalEnricherRegistry(); registry.register(Tag.class, (ctx, appender) -> { NamespaceAndName repository = ctx.oneRequireByType(NamespaceAndName.class); Tag tag = ctx.oneRequireByType(Tag.class); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserToUserDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserToUserDtoMapperTest.java index 9924dae81b..708b9e1e72 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserToUserDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserToUserDtoMapperTest.java @@ -155,7 +155,7 @@ public class UserToUserDtoMapperTest { public void shouldAppendLink() { User trillian = UserTestData.createTrillian(); - LinkEnricherRegistry registry = new LinkEnricherRegistry(); + HalEnricherRegistry registry = new HalEnricherRegistry(); registry.register(User.class, (ctx, appender) -> appender.appendOne("sample", "http://" + ctx.oneByType(User.class).get().getName())); mapper.setRegistry(registry); From 462cccb443f275c99242810ea868b361d0d00884 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Mon, 4 Feb 2019 14:41:07 +0100 Subject: [PATCH 080/112] rename HalAppender methods to make clear, that they target links --- .../scm/api/v2/resources/HalAppender.java | 4 ++-- .../v2/resources/HalAppenderMapperTest.java | 20 +++++++++---------- .../api/v2/resources/EdisonHalAppender.java | 4 ++-- .../BranchToBranchDtoMapperTest.java | 2 +- .../v2/resources/EdisonHalAppenderTest.java | 4 ++-- .../FileObjectToFileObjectDtoMapperTest.java | 2 +- .../resources/GroupToGroupDtoMapperTest.java | 2 +- .../api/v2/resources/MeDtoFactoryTest.java | 2 +- .../RepositoryToRepositoryDtoMapperTest.java | 2 +- .../v2/resources/TagToTagDtoMapperTest.java | 2 +- .../v2/resources/UserToUserDtoMapperTest.java | 2 +- 11 files changed, 23 insertions(+), 23 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/HalAppender.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/HalAppender.java index 14cd5153e6..4b7687133f 100644 --- a/scm-core/src/main/java/sonia/scm/api/v2/resources/HalAppender.java +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/HalAppender.java @@ -14,7 +14,7 @@ public interface HalAppender { * @param rel name of relation * @param href link uri */ - void appendOne(String rel, String href); + void appendLink(String rel, String href); /** * Returns a builder which is able to append an array of links to the resource. @@ -22,7 +22,7 @@ public interface HalAppender { * @param rel name of link relation * @return multi link builder */ - LinkArrayBuilder arrayBuilder(String rel); + LinkArrayBuilder linkArrayBuilder(String rel); /** diff --git a/scm-core/src/test/java/sonia/scm/api/v2/resources/HalAppenderMapperTest.java b/scm-core/src/test/java/sonia/scm/api/v2/resources/HalAppenderMapperTest.java index 639671f423..2505131a9a 100644 --- a/scm-core/src/test/java/sonia/scm/api/v2/resources/HalAppenderMapperTest.java +++ b/scm-core/src/test/java/sonia/scm/api/v2/resources/HalAppenderMapperTest.java @@ -28,34 +28,34 @@ class HalAppenderMapperTest { @Test void shouldAppendSimpleLink() { - registry.register(String.class, (ctx, appender) -> appender.appendOne("42", "https://hitchhiker.com")); + registry.register(String.class, (ctx, appender) -> appender.appendLink("42", "https://hitchhiker.com")); mapper.appendLinks(appender, "hello"); - verify(appender).appendOne("42", "https://hitchhiker.com"); + verify(appender).appendLink("42", "https://hitchhiker.com"); } @Test void shouldCallMultipleEnrichers() { - registry.register(String.class, (ctx, appender) -> appender.appendOne("42", "https://hitchhiker.com")); - registry.register(String.class, (ctx, appender) -> appender.appendOne("21", "https://scm.hitchhiker.com")); + registry.register(String.class, (ctx, appender) -> appender.appendLink("42", "https://hitchhiker.com")); + registry.register(String.class, (ctx, appender) -> appender.appendLink("21", "https://scm.hitchhiker.com")); mapper.appendLinks(appender, "hello"); - verify(appender).appendOne("42", "https://hitchhiker.com"); - verify(appender).appendOne("21", "https://scm.hitchhiker.com"); + verify(appender).appendLink("42", "https://hitchhiker.com"); + verify(appender).appendLink("21", "https://scm.hitchhiker.com"); } @Test void shouldAppendLinkByUsingSourceFromContext() { registry.register(String.class, (ctx, appender) -> { Optional<String> rel = ctx.oneByType(String.class); - appender.appendOne(rel.get(), "https://hitchhiker.com"); + appender.appendLink(rel.get(), "https://hitchhiker.com"); }); mapper.appendLinks(appender, "42"); - verify(appender).appendOne("42", "https://hitchhiker.com"); + verify(appender).appendLink("42", "https://hitchhiker.com"); } @Test @@ -63,12 +63,12 @@ class HalAppenderMapperTest { registry.register(Integer.class, (ctx, appender) -> { Optional<Integer> rel = ctx.oneByType(Integer.class); Optional<String> href = ctx.oneByType(String.class); - appender.appendOne(String.valueOf(rel.get()), href.get()); + appender.appendLink(String.valueOf(rel.get()), href.get()); }); mapper.appendLinks(appender, Integer.valueOf(42), "https://hitchhiker.com"); - verify(appender).appendOne("42", "https://hitchhiker.com"); + verify(appender).appendLink("42", "https://hitchhiker.com"); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/EdisonHalAppender.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/EdisonHalAppender.java index 14db542976..7d25f26e53 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/EdisonHalAppender.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/EdisonHalAppender.java @@ -15,12 +15,12 @@ class EdisonHalAppender implements HalAppender { } @Override - public void appendOne(String rel, String href) { + public void appendLink(String rel, String href) { builder.single(Link.link(rel, href)); } @Override - public LinkArrayBuilder arrayBuilder(String rel) { + public LinkArrayBuilder linkArrayBuilder(String rel) { return new EdisonLinkArrayBuilder(builder, rel); } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapperTest.java index 7b19603218..3e64ab95b6 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapperTest.java @@ -29,7 +29,7 @@ class BranchToBranchDtoMapperTest { NamespaceAndName namespaceAndName = ctx.oneRequireByType(NamespaceAndName.class); Branch branch = ctx.oneRequireByType(Branch.class); - appender.appendOne("ka", "http://" + namespaceAndName.logString() + "/" + branch.getName()); + appender.appendLink("ka", "http://" + namespaceAndName.logString() + "/" + branch.getName()); }); mapper.setRegistry(registry); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/EdisonHalAppenderTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/EdisonHalAppenderTest.java index 8b326b9f05..766cd2f863 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/EdisonHalAppenderTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/EdisonHalAppenderTest.java @@ -23,7 +23,7 @@ class EdisonHalAppenderTest { @Test void shouldAppendOneLink() { - appender.appendOne("self", "https://scm.hitchhiker.com"); + appender.appendLink("self", "https://scm.hitchhiker.com"); Links links = builder.build(); assertThat(links.getLinkBy("self").get().getHref()).isEqualTo("https://scm.hitchhiker.com"); @@ -31,7 +31,7 @@ class EdisonHalAppenderTest { @Test void shouldAppendMultipleLinks() { - appender.arrayBuilder("items") + appender.linkArrayBuilder("items") .append("one", "http://one") .append("two", "http://two") .build(); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapperTest.java index 9f399fe93b..55058a1684 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapperTest.java @@ -79,7 +79,7 @@ public class FileObjectToFileObjectDtoMapperTest { FileObject fo = ctx.oneRequireByType(FileObject.class); String rev = ctx.oneRequireByType(String.class); - appender.appendOne("hog", "http://" + repository.logString() + "/" + fo.getName() + "/" + rev); + appender.appendLink("hog", "http://" + repository.logString() + "/" + fo.getName() + "/" + rev); }); mapper.setRegistry(registry); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupToGroupDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupToGroupDtoMapperTest.java index bb1e9144b2..045124ad91 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupToGroupDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupToGroupDtoMapperTest.java @@ -93,7 +93,7 @@ public class GroupToGroupDtoMapperTest { HalEnricherRegistry registry = new HalEnricherRegistry(); registry.register(Group.class, (ctx, appender) -> { Group group = ctx.oneRequireByType(Group.class); - appender.appendOne("some", "http://" + group.getName()); + appender.appendLink("some", "http://" + group.getName()); }); mapper.setRegistry(registry); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeDtoFactoryTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeDtoFactoryTest.java index 226a09231d..8a00c69229 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeDtoFactoryTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeDtoFactoryTest.java @@ -174,7 +174,7 @@ class MeDtoFactoryTest { registry.register(Me.class, (ctx, appender) -> { User user = ctx.oneRequireByType(User.class); - appender.appendOne("profile", "http://hitchhiker.com/users/" + user.getName()); + appender.appendLink("profile", "http://hitchhiker.com/users/" + user.getName()); }); MeDto dto = meDtoFactory.create(); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java index 9a478d1ecb..9df7273680 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java @@ -214,7 +214,7 @@ public class RepositoryToRepositoryDtoMapperTest { HalEnricherRegistry registry = new HalEnricherRegistry(); registry.register(Repository.class, (ctx, appender) -> { Repository repository = ctx.oneRequireByType(Repository.class); - appender.appendOne("id", "http://" + repository.getId()); + appender.appendLink("id", "http://" + repository.getId()); }); mapper.setRegistry(registry); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagToTagDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagToTagDtoMapperTest.java index 03e726a197..cd3c18de27 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagToTagDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagToTagDtoMapperTest.java @@ -26,7 +26,7 @@ class TagToTagDtoMapperTest { registry.register(Tag.class, (ctx, appender) -> { NamespaceAndName repository = ctx.oneRequireByType(NamespaceAndName.class); Tag tag = ctx.oneRequireByType(Tag.class); - appender.appendOne("yo", "http://" + repository.logString() + "/" + tag.getName()); + appender.appendLink("yo", "http://" + repository.logString() + "/" + tag.getName()); }); mapper.setRegistry(registry); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserToUserDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserToUserDtoMapperTest.java index 708b9e1e72..ae1d75dddf 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserToUserDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserToUserDtoMapperTest.java @@ -156,7 +156,7 @@ public class UserToUserDtoMapperTest { User trillian = UserTestData.createTrillian(); HalEnricherRegistry registry = new HalEnricherRegistry(); - registry.register(User.class, (ctx, appender) -> appender.appendOne("sample", "http://" + ctx.oneByType(User.class).get().getName())); + registry.register(User.class, (ctx, appender) -> appender.appendLink("sample", "http://" + ctx.oneByType(User.class).get().getName())); mapper.setRegistry(registry); UserDto userDto = mapper.map(trillian); From e6335367e756f6c2e88e97f1c75cae2f1cb001ca Mon Sep 17 00:00:00 2001 From: Mohamed Karray <mohamed.karray.sr@gmail.com> Date: Mon, 4 Feb 2019 15:20:34 +0100 Subject: [PATCH 081/112] add extension point for the redirect route --- scm-ui/src/containers/Main.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scm-ui/src/containers/Main.js b/scm-ui/src/containers/Main.js index d2bc50faf2..a8b6c056b6 100644 --- a/scm-ui/src/containers/Main.js +++ b/scm-ui/src/containers/Main.js @@ -35,7 +35,12 @@ class Main extends React.Component<Props> { return ( <div className="main"> <Switch> - <Redirect exact path="/" to="/repos" /> + <ExtensionPoint + name="redirect-route" + props={{authenticated, links}} + > + <Redirect exact path="/" to="/repos"/> + </ExtensionPoint> <Route exact path="/login" component={Login} /> <Route path="/logout" component={Logout} /> <ProtectedRoute From 21441aed45884181d838ed9c93b8ba78162244d0 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Mon, 4 Feb 2019 16:18:47 +0100 Subject: [PATCH 082/112] added appendEmbedded method to HalAppender With this change we could not longer use @AfterMapping from MapStruct. We should use @ObjectFactory instead and create Links and Embedded before. --- .../scm/api/v2/resources/HalAppender.java | 9 +++++ .../api/v2/resources/HalAppenderMapper.java | 2 +- .../v2/resources/HalAppenderMapperTest.java | 8 ++-- .../sonia/scm/api/v2/resources/BranchDto.java | 6 +-- .../v2/resources/BranchToBranchDtoMapper.java | 21 +++++----- .../scm/api/v2/resources/ChangesetDto.java | 15 ++------ .../ChangesetToChangesetDtoMapper.java | 29 +++++++------- .../api/v2/resources/EdisonHalAppender.java | 19 +++++++--- .../scm/api/v2/resources/FileObjectDto.java | 7 ++-- .../FileObjectToFileObjectDtoMapper.java | 16 ++++---- .../sonia/scm/api/v2/resources/GroupDto.java | 11 ++---- .../v2/resources/GroupToGroupDtoMapper.java | 31 ++++++++------- .../sonia/scm/api/v2/resources/IndexDto.java | 5 ++- .../api/v2/resources/IndexDtoGenerator.java | 7 +++- .../sonia/scm/api/v2/resources/MeDto.java | 7 ++-- .../scm/api/v2/resources/MeDtoFactory.java | 19 +++++----- .../scm/api/v2/resources/RepositoryDto.java | 10 ++--- .../RepositoryToRepositoryDtoMapper.java | 38 ++++++++++--------- .../sonia/scm/api/v2/resources/TagDto.java | 7 ++-- .../api/v2/resources/TagToTagDtoMapper.java | 20 +++++----- .../sonia/scm/api/v2/resources/UserDto.java | 7 ++-- .../api/v2/resources/UserToUserDtoMapper.java | 24 ++++++------ .../v2/resources/EdisonHalAppenderTest.java | 28 +++++++++++--- .../test/java/sonia/scm/it/GitLfsITCase.java | 11 ++++-- 24 files changed, 196 insertions(+), 161 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/HalAppender.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/HalAppender.java index 4b7687133f..a7beaf1f6e 100644 --- a/scm-core/src/main/java/sonia/scm/api/v2/resources/HalAppender.java +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/HalAppender.java @@ -1,5 +1,7 @@ package sonia.scm.api.v2.resources; +import de.otto.edison.hal.HalRepresentation; + /** * The {@link HalAppender} can be used within an {@link HalEnricher} to append hateoas links to a json response. * @@ -24,6 +26,13 @@ public interface HalAppender { */ LinkArrayBuilder linkArrayBuilder(String rel); + /** + * Appends one embedded to the json response. + * + * @param rel name of relation + * @param embeddedItem embedded object + */ + void appendEmbedded(String rel, HalRepresentation embeddedItem); /** * Builder for link arrays. diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/HalAppenderMapper.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/HalAppenderMapper.java index 04310efad2..dd49b765bc 100644 --- a/scm-core/src/main/java/sonia/scm/api/v2/resources/HalAppenderMapper.java +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/HalAppenderMapper.java @@ -14,7 +14,7 @@ public class HalAppenderMapper { this.registry = registry; } - protected void appendLinks(HalAppender appender, Object source, Object... contextEntries) { + protected void applyEnrichers(HalAppender appender, Object source, Object... contextEntries) { // null check is only their to not break existing tests if (registry != null) { diff --git a/scm-core/src/test/java/sonia/scm/api/v2/resources/HalAppenderMapperTest.java b/scm-core/src/test/java/sonia/scm/api/v2/resources/HalAppenderMapperTest.java index 2505131a9a..ff658cc26a 100644 --- a/scm-core/src/test/java/sonia/scm/api/v2/resources/HalAppenderMapperTest.java +++ b/scm-core/src/test/java/sonia/scm/api/v2/resources/HalAppenderMapperTest.java @@ -30,7 +30,7 @@ class HalAppenderMapperTest { void shouldAppendSimpleLink() { registry.register(String.class, (ctx, appender) -> appender.appendLink("42", "https://hitchhiker.com")); - mapper.appendLinks(appender, "hello"); + mapper.applyEnrichers(appender, "hello"); verify(appender).appendLink("42", "https://hitchhiker.com"); } @@ -40,7 +40,7 @@ class HalAppenderMapperTest { registry.register(String.class, (ctx, appender) -> appender.appendLink("42", "https://hitchhiker.com")); registry.register(String.class, (ctx, appender) -> appender.appendLink("21", "https://scm.hitchhiker.com")); - mapper.appendLinks(appender, "hello"); + mapper.applyEnrichers(appender, "hello"); verify(appender).appendLink("42", "https://hitchhiker.com"); verify(appender).appendLink("21", "https://scm.hitchhiker.com"); @@ -53,7 +53,7 @@ class HalAppenderMapperTest { appender.appendLink(rel.get(), "https://hitchhiker.com"); }); - mapper.appendLinks(appender, "42"); + mapper.applyEnrichers(appender, "42"); verify(appender).appendLink("42", "https://hitchhiker.com"); } @@ -66,7 +66,7 @@ class HalAppenderMapperTest { appender.appendLink(String.valueOf(rel.get()), href.get()); }); - mapper.appendLinks(appender, Integer.valueOf(42), "https://hitchhiker.com"); + mapper.applyEnrichers(appender, Integer.valueOf(42), "https://hitchhiker.com"); verify(appender).appendLink("42", "https://hitchhiker.com"); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDto.java index f5bdc850ab..343d9c8bc8 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDto.java @@ -1,5 +1,6 @@ package sonia.scm.api.v2.resources; +import de.otto.edison.hal.Embedded; import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.Links; import lombok.Getter; @@ -12,8 +13,7 @@ public class BranchDto extends HalRepresentation { private String name; private String revision; - @Override - protected HalRepresentation add(Links links) { - return super.add(links); + BranchDto(Links links, Embedded embedded) { + super(links, embedded); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapper.java index 0fdd16fa56..c940b1ffd9 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapper.java @@ -1,11 +1,11 @@ package sonia.scm.api.v2.resources; +import de.otto.edison.hal.Embedded; import de.otto.edison.hal.Links; -import org.mapstruct.AfterMapping; import org.mapstruct.Context; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.MappingTarget; +import org.mapstruct.ObjectFactory; import sonia.scm.repository.Branch; import sonia.scm.repository.NamespaceAndName; @@ -23,16 +23,17 @@ public abstract class BranchToBranchDtoMapper extends HalAppenderMapper { @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes public abstract BranchDto map(Branch branch, @Context NamespaceAndName namespaceAndName); - @AfterMapping - void appendLinks(Branch source, @MappingTarget BranchDto target, @Context NamespaceAndName namespaceAndName) { + @ObjectFactory + BranchDto createDto(@Context NamespaceAndName namespaceAndName, Branch branch) { Links.Builder linksBuilder = linkingTo() - .self(resourceLinks.branch().self(namespaceAndName, target.getName())) - .single(linkBuilder("history", resourceLinks.branch().history(namespaceAndName, target.getName())).build()) - .single(linkBuilder("changeset", resourceLinks.changeset().changeset(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())).build()) - .single(linkBuilder("source", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())).build()); + .self(resourceLinks.branch().self(namespaceAndName, branch.getName())) + .single(linkBuilder("history", resourceLinks.branch().history(namespaceAndName, branch.getName())).build()) + .single(linkBuilder("changeset", resourceLinks.changeset().changeset(namespaceAndName.getNamespace(), namespaceAndName.getName(), branch.getRevision())).build()) + .single(linkBuilder("source", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), branch.getRevision())).build()); - appendLinks(new EdisonHalAppender(linksBuilder), source, namespaceAndName); + Embedded.Builder embeddedBuilder = Embedded.embeddedBuilder(); + applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), branch, namespaceAndName); - target.add(linksBuilder.build()); + return new BranchDto(linksBuilder.build(), embeddedBuilder.build()); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetDto.java index 162d5d6699..6759f5cb8c 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetDto.java @@ -1,5 +1,6 @@ package sonia.scm.api.v2.resources; +import de.otto.edison.hal.Embedded; import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.Links; import lombok.Getter; @@ -7,7 +8,6 @@ import lombok.NoArgsConstructor; import lombok.Setter; import java.time.Instant; -import java.util.List; @Getter @Setter @@ -34,16 +34,7 @@ public class ChangesetDto extends HalRepresentation { */ private String description; - @Override - @SuppressWarnings("squid:S1185") // We want to have this method available in this package - protected HalRepresentation add(Links links) { - return super.add(links); + public ChangesetDto(Links links, Embedded embedded) { + super(links, embedded); } - - @SuppressWarnings("squid:S1185") // We want to have this method available in this package - protected HalRepresentation withEmbedded(String rel, List<? extends HalRepresentation> halRepresentations) { - return super.withEmbedded(rel, halRepresentations); - } - - } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java index 25bd0ef661..16e33aac5f 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java @@ -1,11 +1,11 @@ package sonia.scm.api.v2.resources; +import de.otto.edison.hal.Embedded; import de.otto.edison.hal.Links; -import org.mapstruct.AfterMapping; import org.mapstruct.Context; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.MappingTarget; +import org.mapstruct.ObjectFactory; import sonia.scm.repository.Branch; import sonia.scm.repository.Changeset; import sonia.scm.repository.Repository; @@ -19,6 +19,7 @@ import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; +import static de.otto.edison.hal.Embedded.embeddedBuilder; import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Links.linkingTo; @@ -31,7 +32,6 @@ public abstract class ChangesetToChangesetDtoMapper extends HalAppenderMapper im @Inject private ResourceLinks resourceLinks; - @Inject private BranchCollectionToDtoMapper branchCollectionToDtoMapper; @@ -46,31 +46,34 @@ public abstract class ChangesetToChangesetDtoMapper extends HalAppenderMapper im public abstract ChangesetDto map(Changeset changeset, @Context Repository repository); - @AfterMapping - void appendLinks(Changeset source, @MappingTarget ChangesetDto target, @Context Repository repository) { + @ObjectFactory + ChangesetDto createDto(@Context Repository repository, Changeset source) { String namespace = repository.getNamespace(); String name = repository.getName(); + Embedded.Builder embeddedBuilder = embeddedBuilder(); + try (RepositoryService repositoryService = serviceFactory.create(repository)) { if (repositoryService.isSupported(Command.TAGS)) { - target.withEmbedded("tags", tagCollectionToDtoMapper.getTagDtoList(namespace, name, + embeddedBuilder.with("tags", tagCollectionToDtoMapper.getTagDtoList(namespace, name, getListOfObjects(source.getTags(), tagName -> new Tag(tagName, source.getId())))); } if (repositoryService.isSupported(Command.BRANCHES)) { - target.withEmbedded("branches", branchCollectionToDtoMapper.getBranchDtoList(namespace, name, + embeddedBuilder.with("branches", branchCollectionToDtoMapper.getBranchDtoList(namespace, name, getListOfObjects(source.getBranches(), branchName -> new Branch(branchName, source.getId())))); } } - target.withEmbedded("parents", getListOfObjects(source.getParents(), parent -> changesetToParentDtoMapper.map(new Changeset(parent, 0L, null), repository))); + embeddedBuilder.with("parents", getListOfObjects(source.getParents(), parent -> changesetToParentDtoMapper.map(new Changeset(parent, 0L, null), repository))); Links.Builder linksBuilder = linkingTo() - .self(resourceLinks.changeset().self(repository.getNamespace(), repository.getName(), target.getId())) - .single(link("diff", resourceLinks.diff().self(namespace, name, target.getId()))) - .single(link("modifications", resourceLinks.modifications().self(namespace, name, target.getId()))); + .self(resourceLinks.changeset().self(repository.getNamespace(), repository.getName(), source.getId())) + .single(link("diff", resourceLinks.diff().self(namespace, name, source.getId()))) + .single(link("modifications", resourceLinks.modifications().self(namespace, name, source.getId()))); - appendLinks(new EdisonHalAppender(linksBuilder), source, repository); - target.add(linksBuilder.build()); + applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), source, repository); + + return new ChangesetDto(linksBuilder.build(), embeddedBuilder.build()); } private <T> List<T> getListOfObjects(List<String> list, Function<String, T> mapFunction) { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/EdisonHalAppender.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/EdisonHalAppender.java index 7d25f26e53..769de2b705 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/EdisonHalAppender.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/EdisonHalAppender.java @@ -1,5 +1,7 @@ package sonia.scm.api.v2.resources; +import de.otto.edison.hal.Embedded; +import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.Link; import de.otto.edison.hal.Links; @@ -8,20 +10,27 @@ import java.util.List; class EdisonHalAppender implements HalAppender { - private final Links.Builder builder; + private final Links.Builder linkBuilder; + private final Embedded.Builder embeddedBuilder; - EdisonHalAppender(Links.Builder builder) { - this.builder = builder; + EdisonHalAppender(Links.Builder linkBuilder, Embedded.Builder embeddedBuilder) { + this.linkBuilder = linkBuilder; + this.embeddedBuilder = embeddedBuilder; } @Override public void appendLink(String rel, String href) { - builder.single(Link.link(rel, href)); + linkBuilder.single(Link.link(rel, href)); } @Override public LinkArrayBuilder linkArrayBuilder(String rel) { - return new EdisonLinkArrayBuilder(builder, rel); + return new EdisonLinkArrayBuilder(linkBuilder, rel); + } + + @Override + public void appendEmbedded(String rel, HalRepresentation embedded) { + embeddedBuilder.with(rel, embedded); } private static class EdisonLinkArrayBuilder implements LinkArrayBuilder { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java index c183d731c6..0bce564e35 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java @@ -1,6 +1,7 @@ package sonia.scm.api.v2.resources; import com.fasterxml.jackson.annotation.JsonInclude; +import de.otto.edison.hal.Embedded; import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.Links; import lombok.Getter; @@ -27,10 +28,8 @@ public class FileObjectDto extends HalRepresentation { @JsonInclude(JsonInclude.Include.NON_EMPTY) private String revision; - @Override - @SuppressWarnings("squid:S1185") // We want to have this method available in this package - protected HalRepresentation add(Links links) { - return super.add(links); + public FileObjectDto(Links links, Embedded embedded) { + super(links, embedded); } public void setChildren(List<FileObjectDto> children) { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java index 132abce71b..608dea9f26 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectToFileObjectDtoMapper.java @@ -1,17 +1,18 @@ package sonia.scm.api.v2.resources; +import de.otto.edison.hal.Embedded; import de.otto.edison.hal.Links; -import org.mapstruct.AfterMapping; import org.mapstruct.Context; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.MappingTarget; +import org.mapstruct.ObjectFactory; import sonia.scm.repository.FileObject; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.SubRepository; import javax.inject.Inject; +import static de.otto.edison.hal.Embedded.embeddedBuilder; import static de.otto.edison.hal.Link.link; @Mapper @@ -25,20 +26,21 @@ public abstract class FileObjectToFileObjectDtoMapper extends HalAppenderMapper abstract SubRepositoryDto mapSubrepository(SubRepository subRepository); - @AfterMapping - void addLinks(FileObject fileObject, @MappingTarget FileObjectDto dto, @Context NamespaceAndName namespaceAndName, @Context String revision) { + @ObjectFactory + FileObjectDto createDto(@Context NamespaceAndName namespaceAndName, @Context String revision, FileObject fileObject) { String path = removeFirstSlash(fileObject.getPath()); Links.Builder links = Links.linkingTo(); - if (dto.isDirectory()) { + if (fileObject.isDirectory()) { links.self(resourceLinks.source().sourceWithPath(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, path)); } else { links.self(resourceLinks.source().content(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, path)); links.single(link("history", resourceLinks.fileHistory().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, path))); } - appendLinks(new EdisonHalAppender(links), fileObject, namespaceAndName, revision); + Embedded.Builder embeddedBuilder = embeddedBuilder(); + applyEnrichers(new EdisonHalAppender(links, embeddedBuilder), fileObject, namespaceAndName, revision); - dto.add(links.build()); + return new FileObjectDto(links.build(), embeddedBuilder.build()); } private String removeFirstSlash(String source) { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java index 760beab1da..2ccfcff38e 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java @@ -1,6 +1,7 @@ package sonia.scm.api.v2.resources; import com.fasterxml.jackson.annotation.JsonInclude; +import de.otto.edison.hal.Embedded; import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.Links; import lombok.Getter; @@ -28,13 +29,7 @@ public class GroupDto extends HalRepresentation { private Map<String, String> properties; private List<String> members; - @Override - @SuppressWarnings("squid:S1185") // We want to have this method available in this package - protected HalRepresentation add(Links links) { - return super.add(links); - } - - public HalRepresentation withMembers(List<MemberDto> members) { - return super.withEmbedded("members", members); + GroupDto(Links links, Embedded embedded) { + super(links, embedded); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupToGroupDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupToGroupDtoMapper.java index a728632395..7d5ddae548 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupToGroupDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupToGroupDtoMapper.java @@ -1,9 +1,9 @@ package sonia.scm.api.v2.resources; +import de.otto.edison.hal.Embedded; import de.otto.edison.hal.Links; -import org.mapstruct.AfterMapping; import org.mapstruct.Mapper; -import org.mapstruct.MappingTarget; +import org.mapstruct.ObjectFactory; import sonia.scm.group.Group; import sonia.scm.group.GroupPermissions; import sonia.scm.security.PermissionPermissions; @@ -12,6 +12,7 @@ import javax.inject.Inject; import java.util.List; import java.util.stream.Collectors; +import static de.otto.edison.hal.Embedded.embeddedBuilder; import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Links.linkingTo; @@ -23,28 +24,26 @@ public abstract class GroupToGroupDtoMapper extends BaseMapper<Group, GroupDto> @Inject private ResourceLinks resourceLinks; - @AfterMapping - void appendLinks(Group group, @MappingTarget GroupDto target) { - Links.Builder linksBuilder = linkingTo().self(resourceLinks.group().self(target.getName())); + @ObjectFactory + GroupDto createDto(Group group) { + Links.Builder linksBuilder = linkingTo().self(resourceLinks.group().self(group.getName())); if (GroupPermissions.delete(group).isPermitted()) { - linksBuilder.single(link("delete", resourceLinks.group().delete(target.getName()))); + linksBuilder.single(link("delete", resourceLinks.group().delete(group.getName()))); } if (GroupPermissions.modify(group).isPermitted()) { - linksBuilder.single(link("update", resourceLinks.group().update(target.getName()))); + linksBuilder.single(link("update", resourceLinks.group().update(group.getName()))); } if (PermissionPermissions.read().isPermitted()) { - linksBuilder.single(link("permissions", resourceLinks.groupPermissions().permissions(target.getName()))); + linksBuilder.single(link("permissions", resourceLinks.groupPermissions().permissions(group.getName()))); } - appendLinks(new EdisonHalAppender(linksBuilder), group); - - target.add(linksBuilder.build()); - } - - @AfterMapping - void mapMembers(Group group, @MappingTarget GroupDto target) { + Embedded.Builder embeddedBuilder = embeddedBuilder(); List<MemberDto> memberDtos = group.getMembers().stream().map(this::createMember).collect(Collectors.toList()); - target.withMembers(memberDtos); + embeddedBuilder.with("members", memberDtos); + + applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), group); + + return new GroupDto(linksBuilder.build(), embeddedBuilder.build()); } private MemberDto createMember(String name) { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDto.java index 9346420f58..16f945332d 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDto.java @@ -1,5 +1,6 @@ package sonia.scm.api.v2.resources; +import de.otto.edison.hal.Embedded; import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.Links; import lombok.Getter; @@ -9,8 +10,8 @@ public class IndexDto extends HalRepresentation { private final String version; - IndexDto(String version, Links links) { - super(links); + IndexDto(Links links, Embedded embedded, String version) { + super(links, embedded); this.version = version; } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java index ed848c4311..90445bcdc2 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java @@ -1,6 +1,7 @@ package sonia.scm.api.v2.resources; import com.google.common.collect.Lists; +import de.otto.edison.hal.Embedded; import de.otto.edison.hal.Link; import de.otto.edison.hal.Links; import org.apache.shiro.SecurityUtils; @@ -13,6 +14,7 @@ import sonia.scm.user.UserPermissions; import javax.inject.Inject; import java.util.List; +import static de.otto.edison.hal.Embedded.embeddedBuilder; import static de.otto.edison.hal.Link.link; public class IndexDtoGenerator extends HalAppenderMapper { @@ -61,8 +63,9 @@ public class IndexDtoGenerator extends HalAppenderMapper { builder.single(link("login", resourceLinks.authentication().jsonLogin())); } - appendLinks(new EdisonHalAppender(builder), new Index()); + Embedded.Builder embeddedBuilder = embeddedBuilder(); + applyEnrichers(new EdisonHalAppender(builder, embeddedBuilder), new Index()); - return new IndexDto(scmContextProvider.getVersion(), builder.build()); + return new IndexDto(builder.build(), embeddedBuilder.build(), scmContextProvider.getVersion()); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeDto.java index 5488faca28..84fbbfe290 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeDto.java @@ -1,5 +1,6 @@ package sonia.scm.api.v2.resources; +import de.otto.edison.hal.Embedded; import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.Links; import lombok.Getter; @@ -18,9 +19,7 @@ public class MeDto extends HalRepresentation { private String mail; private List<String> groups; - @Override - @SuppressWarnings("squid:S1185") // We want to have this method available in this package - protected HalRepresentation add(Links links) { - return super.add(links); + MeDto(Links links, Embedded embedded) { + super(links, embedded); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeDtoFactory.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeDtoFactory.java index 8763c54565..b5e1998066 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeDtoFactory.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeDtoFactory.java @@ -1,6 +1,7 @@ package sonia.scm.api.v2.resources; import com.google.common.collect.ImmutableList; +import de.otto.edison.hal.Embedded; import de.otto.edison.hal.Links; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.PrincipalCollection; @@ -13,6 +14,7 @@ import sonia.scm.user.UserPermissions; import javax.inject.Inject; import java.util.Collections; +import static de.otto.edison.hal.Embedded.embeddedBuilder; import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Links.linkingTo; @@ -29,15 +31,11 @@ public class MeDtoFactory extends HalAppenderMapper { public MeDto create() { PrincipalCollection principals = getPrincipalCollection(); - - MeDto dto = new MeDto(); - User user = principals.oneByType(User.class); + MeDto dto = createDto(user); mapUserProperties(user, dto); mapGroups(principals, dto); - - appendLinks(user, dto); return dto; } @@ -61,21 +59,22 @@ public class MeDtoFactory extends HalAppenderMapper { } - private void appendLinks(User user, MeDto target) { + private MeDto createDto(User user) { Links.Builder linksBuilder = linkingTo().self(resourceLinks.me().self()); if (UserPermissions.delete(user).isPermitted()) { - linksBuilder.single(link("delete", resourceLinks.me().delete(target.getName()))); + linksBuilder.single(link("delete", resourceLinks.me().delete(user.getName()))); } if (UserPermissions.modify(user).isPermitted()) { - linksBuilder.single(link("update", resourceLinks.me().update(target.getName()))); + linksBuilder.single(link("update", resourceLinks.me().update(user.getName()))); } if (userManager.isTypeDefault(user) && UserPermissions.changePassword(user).isPermitted()) { linksBuilder.single(link("password", resourceLinks.me().passwordChange())); } - appendLinks(new EdisonHalAppender(linksBuilder), new Me(), user); + Embedded.Builder embeddedBuilder = embeddedBuilder(); + applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), new Me(), user); - target.add(linksBuilder.build()); + return new MeDto(linksBuilder.build(), embeddedBuilder.build()); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDto.java index ddfe432d73..8b48311bba 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDto.java @@ -1,9 +1,11 @@ package sonia.scm.api.v2.resources; import com.fasterxml.jackson.annotation.JsonInclude; +import de.otto.edison.hal.Embedded; import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.Links; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.NotEmpty; @@ -13,7 +15,7 @@ import java.time.Instant; import java.util.List; import java.util.Map; -@Getter @Setter +@Getter @Setter @NoArgsConstructor public class RepositoryDto extends HalRepresentation { @Email @@ -31,9 +33,7 @@ public class RepositoryDto extends HalRepresentation { private String type; protected Map<String, String> properties; - @Override - @SuppressWarnings("squid:S1185") // We want to have this method available in this package - protected HalRepresentation add(Links links) { - return super.add(links); + RepositoryDto(Links links, Embedded embedded) { + super(links, embedded); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java index de03f1aa05..9e680b7e5c 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java @@ -1,11 +1,11 @@ package sonia.scm.api.v2.resources; import com.google.inject.Inject; +import de.otto.edison.hal.Embedded; import de.otto.edison.hal.Link; import de.otto.edison.hal.Links; -import org.mapstruct.AfterMapping; import org.mapstruct.Mapper; -import org.mapstruct.MappingTarget; +import org.mapstruct.ObjectFactory; import sonia.scm.repository.Feature; import sonia.scm.repository.HealthCheckFailure; import sonia.scm.repository.Repository; @@ -17,6 +17,7 @@ import sonia.scm.repository.api.ScmProtocol; import java.util.List; +import static de.otto.edison.hal.Embedded.embeddedBuilder; import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Links.linkingTo; import static java.util.stream.Collectors.toList; @@ -33,17 +34,17 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit abstract HealthCheckFailureDto toDto(HealthCheckFailure failure); - @AfterMapping - void appendLinks(Repository repository, @MappingTarget RepositoryDto target) { - Links.Builder linksBuilder = linkingTo().self(resourceLinks.repository().self(target.getNamespace(), target.getName())); + @ObjectFactory + RepositoryDto createDto(Repository repository) { + Links.Builder linksBuilder = linkingTo().self(resourceLinks.repository().self(repository.getNamespace(), repository.getName())); if (RepositoryPermissions.delete(repository).isPermitted()) { - linksBuilder.single(link("delete", resourceLinks.repository().delete(target.getNamespace(), target.getName()))); + linksBuilder.single(link("delete", resourceLinks.repository().delete(repository.getNamespace(), repository.getName()))); } if (RepositoryPermissions.modify(repository).isPermitted()) { - linksBuilder.single(link("update", resourceLinks.repository().update(target.getNamespace(), target.getName()))); + linksBuilder.single(link("update", resourceLinks.repository().update(repository.getNamespace(), repository.getName()))); } if (RepositoryPermissions.permissionRead(repository).isPermitted()) { - linksBuilder.single(link("permissions", resourceLinks.repositoryPermission().all(target.getNamespace(), target.getName()))); + linksBuilder.single(link("permissions", resourceLinks.repositoryPermission().all(repository.getNamespace(), repository.getName()))); } try (RepositoryService repositoryService = serviceFactory.create(repository)) { if (RepositoryPermissions.pull(repository).isPermitted()) { @@ -53,26 +54,27 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit linksBuilder.array(protocolLinks); } if (repositoryService.isSupported(Command.TAGS)) { - linksBuilder.single(link("tags", resourceLinks.tag().all(target.getNamespace(), target.getName()))); + linksBuilder.single(link("tags", resourceLinks.tag().all(repository.getNamespace(), repository.getName()))); } if (repositoryService.isSupported(Command.BRANCHES)) { - linksBuilder.single(link("branches", resourceLinks.branchCollection().self(target.getNamespace(), target.getName()))); + linksBuilder.single(link("branches", resourceLinks.branchCollection().self(repository.getNamespace(), repository.getName()))); } if (repositoryService.isSupported(Feature.INCOMING_REVISION)) { - linksBuilder.single(link("incomingChangesets", resourceLinks.incoming().changesets(target.getNamespace(), target.getName()))); - linksBuilder.single(link("incomingDiff", resourceLinks.incoming().diff(target.getNamespace(), target.getName()))); + linksBuilder.single(link("incomingChangesets", resourceLinks.incoming().changesets(repository.getNamespace(), repository.getName()))); + linksBuilder.single(link("incomingDiff", resourceLinks.incoming().diff(repository.getNamespace(), repository.getName()))); } if (repositoryService.isSupported(Command.MERGE)) { - linksBuilder.single(link("merge", resourceLinks.merge().merge(target.getNamespace(), target.getName()))); - linksBuilder.single(link("mergeDryRun", resourceLinks.merge().dryRun(target.getNamespace(), target.getName()))); + linksBuilder.single(link("merge", resourceLinks.merge().merge(repository.getNamespace(), repository.getName()))); + linksBuilder.single(link("mergeDryRun", resourceLinks.merge().dryRun(repository.getNamespace(), repository.getName()))); } } - linksBuilder.single(link("changesets", resourceLinks.changeset().all(target.getNamespace(), target.getName()))); - linksBuilder.single(link("sources", resourceLinks.source().selfWithoutRevision(target.getNamespace(), target.getName()))); + linksBuilder.single(link("changesets", resourceLinks.changeset().all(repository.getNamespace(), repository.getName()))); + linksBuilder.single(link("sources", resourceLinks.source().selfWithoutRevision(repository.getNamespace(), repository.getName()))); - appendLinks(new EdisonHalAppender(linksBuilder), repository); + Embedded.Builder embeddedBuilder = embeddedBuilder(); + applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), repository); - target.add(linksBuilder.build()); + return new RepositoryDto(linksBuilder.build(), embeddedBuilder.build()); } private Link createProtocolLink(ScmProtocol protocol) { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagDto.java index 8af036f5a3..a3d4c5d17e 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagDto.java @@ -1,5 +1,6 @@ package sonia.scm.api.v2.resources; +import de.otto.edison.hal.Embedded; import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.Links; import lombok.Getter; @@ -15,10 +16,8 @@ public class TagDto extends HalRepresentation { private String revision; - @Override - @SuppressWarnings("squid:S1185") // We want to have this method available in this package - protected HalRepresentation add(Links links) { - return super.add(links); + TagDto(Links links, Embedded embedded) { + super(links, embedded); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java index 4e0518a006..3a7faf3155 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java @@ -1,16 +1,17 @@ package sonia.scm.api.v2.resources; +import de.otto.edison.hal.Embedded; import de.otto.edison.hal.Links; -import org.mapstruct.AfterMapping; import org.mapstruct.Context; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.MappingTarget; +import org.mapstruct.ObjectFactory; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Tag; import javax.inject.Inject; +import static de.otto.edison.hal.Embedded.embeddedBuilder; import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Links.linkingTo; @@ -23,15 +24,16 @@ public abstract class TagToTagDtoMapper extends HalAppenderMapper { @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes public abstract TagDto map(Tag tag, @Context NamespaceAndName namespaceAndName); - @AfterMapping - void appendLinks(Tag tag, @MappingTarget TagDto target, @Context NamespaceAndName namespaceAndName) { + @ObjectFactory + TagDto createDto(@Context NamespaceAndName namespaceAndName, Tag tag) { Links.Builder linksBuilder = linkingTo() - .self(resourceLinks.tag().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getName())) - .single(link("sources", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision()))) - .single(link("changeset", resourceLinks.changeset().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision()))); + .self(resourceLinks.tag().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), tag.getName())) + .single(link("sources", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), tag.getRevision()))) + .single(link("changeset", resourceLinks.changeset().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), tag.getRevision()))); - appendLinks(new EdisonHalAppender(linksBuilder), tag, namespaceAndName); + Embedded.Builder embeddedBuilder = embeddedBuilder(); + applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), tag, namespaceAndName); - target.add(linksBuilder.build()); + return new TagDto(linksBuilder.build(), embeddedBuilder.build()); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserDto.java index 9dc5b850bd..0ee7b2f82c 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserDto.java @@ -1,6 +1,7 @@ package sonia.scm.api.v2.resources; import com.fasterxml.jackson.annotation.JsonInclude; +import de.otto.edison.hal.Embedded; import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.Links; import lombok.Getter; @@ -33,9 +34,7 @@ public class UserDto extends HalRepresentation { private String type; private Map<String, String> properties; - @Override - @SuppressWarnings("squid:S1185") // We want to have this method available in this package - protected HalRepresentation add(Links links) { - return super.add(links); + UserDto(Links links, Embedded embedded) { + super(links, embedded); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserToUserDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserToUserDtoMapper.java index 2465293356..ac641e3e66 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserToUserDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserToUserDtoMapper.java @@ -1,10 +1,10 @@ package sonia.scm.api.v2.resources; +import de.otto.edison.hal.Embedded; import de.otto.edison.hal.Links; -import org.mapstruct.AfterMapping; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.MappingTarget; +import org.mapstruct.ObjectFactory; import sonia.scm.security.PermissionPermissions; import sonia.scm.user.User; import sonia.scm.user.UserManager; @@ -12,6 +12,7 @@ import sonia.scm.user.UserPermissions; import javax.inject.Inject; +import static de.otto.edison.hal.Embedded.embeddedBuilder; import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Links.linkingTo; @@ -31,25 +32,26 @@ public abstract class UserToUserDtoMapper extends BaseMapper<User, UserDto> { @Inject private ResourceLinks resourceLinks; - @AfterMapping - protected void appendLinks(User user, @MappingTarget UserDto target) { - Links.Builder linksBuilder = linkingTo().self(resourceLinks.user().self(target.getName())); + @ObjectFactory + UserDto createDto(User user) { + Links.Builder linksBuilder = linkingTo().self(resourceLinks.user().self(user.getName())); if (UserPermissions.delete(user).isPermitted()) { - linksBuilder.single(link("delete", resourceLinks.user().delete(target.getName()))); + linksBuilder.single(link("delete", resourceLinks.user().delete(user.getName()))); } if (UserPermissions.modify(user).isPermitted()) { - linksBuilder.single(link("update", resourceLinks.user().update(target.getName()))); + linksBuilder.single(link("update", resourceLinks.user().update(user.getName()))); if (userManager.isTypeDefault(user)) { - linksBuilder.single(link("password", resourceLinks.user().passwordChange(target.getName()))); + linksBuilder.single(link("password", resourceLinks.user().passwordChange(user.getName()))); } } if (PermissionPermissions.read().isPermitted()) { - linksBuilder.single(link("permissions", resourceLinks.userPermissions().permissions(target.getName()))); + linksBuilder.single(link("permissions", resourceLinks.userPermissions().permissions(user.getName()))); } - appendLinks(new EdisonHalAppender(linksBuilder), user); + Embedded.Builder embeddedBuilder = embeddedBuilder(); + applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), user); - target.add(linksBuilder.build()); + return new UserDto(linksBuilder.build(), embeddedBuilder.build()); } } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/EdisonHalAppenderTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/EdisonHalAppenderTest.java index 766cd2f863..ff149c5dc5 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/EdisonHalAppenderTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/EdisonHalAppenderTest.java @@ -1,5 +1,7 @@ package sonia.scm.api.v2.resources; +import de.otto.edison.hal.Embedded; +import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.Link; import de.otto.edison.hal.Links; import org.junit.jupiter.api.BeforeEach; @@ -7,25 +9,28 @@ import org.junit.jupiter.api.Test; import java.util.List; +import static de.otto.edison.hal.Embedded.embeddedBuilder; import static de.otto.edison.hal.Links.linkingTo; import static org.assertj.core.api.Assertions.assertThat; class EdisonHalAppenderTest { - private Links.Builder builder; + private Links.Builder linksBuilder; + private Embedded.Builder embeddedBuilder; private EdisonHalAppender appender; @BeforeEach void prepare() { - builder = linkingTo(); - appender = new EdisonHalAppender(builder); + linksBuilder = linkingTo(); + embeddedBuilder = embeddedBuilder(); + appender = new EdisonHalAppender(linksBuilder, embeddedBuilder); } @Test void shouldAppendOneLink() { appender.appendLink("self", "https://scm.hitchhiker.com"); - Links links = builder.build(); + Links links = linksBuilder.build(); assertThat(links.getLinkBy("self").get().getHref()).isEqualTo("https://scm.hitchhiker.com"); } @@ -36,8 +41,21 @@ class EdisonHalAppenderTest { .append("two", "http://two") .build(); - List<Link> items = builder.build().getLinksBy("items"); + List<Link> items = linksBuilder.build().getLinksBy("items"); assertThat(items).hasSize(2); } + @Test + void shouldAppendEmbedded() { + HalRepresentation one = new HalRepresentation(); + appender.appendEmbedded("one", one); + + HalRepresentation two = new HalRepresentation(); + appender.appendEmbedded("two", new HalRepresentation()); + + Embedded embedded = embeddedBuilder.build(); + assertThat(embedded.getItemsBy("one")).containsOnly(one); + assertThat(embedded.getItemsBy("two")).containsOnly(two); + } + } diff --git a/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java b/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java index 8cdb162740..a367d171a1 100644 --- a/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java +++ b/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java @@ -136,10 +136,13 @@ public class GitLfsITCase { } private void createUser(User user) { - UserDto dto = new UserToUserDtoMapperImpl(){ - @Override - protected void appendLinks(User user, UserDto target) {} - }.map(user); + UserDto dto = new UserDto(); + dto.setName(user.getName()); + dto.setMail(user.getMail()); + dto.setDisplayName(user.getDisplayName()); + dto.setType(user.getType()); + dto.setActive(user.isActive()); + dto.setAdmin(user.isAdmin()); dto.setPassword(user.getPassword()); createResource(adminClient, "users") .accept("*/*") From 7369f1cfced2c258ad21778cb3c234a1e91032be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= <rene.pfeuffer@cloudogu.com> Date: Mon, 4 Feb 2019 16:33:03 +0100 Subject: [PATCH 083/112] Ensure that verbs occur only once in the collection This is necessary for equals to work correctly --- .../java/sonia/scm/repository/RepositoryPermission.java | 9 +++++---- .../sonia/scm/repository/RepositoryPermissionTest.java | 9 +++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java index 5fd14f2e84..54163e0393 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java @@ -46,9 +46,10 @@ import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; import java.util.Collection; import java.util.LinkedHashSet; +import java.util.Set; import static java.util.Collections.emptyList; -import static java.util.Collections.unmodifiableCollection; +import static java.util.Collections.unmodifiableSet; //~--- JDK imports ------------------------------------------------------------ @@ -67,7 +68,7 @@ public class RepositoryPermission implements PermissionObject, Serializable private boolean groupPermission = false; private String name; @XmlElement(name = "verb") - private Collection<String> verbs; + private Set<String> verbs; /** * Constructs a new {@link RepositoryPermission}. @@ -78,7 +79,7 @@ public class RepositoryPermission implements PermissionObject, Serializable public RepositoryPermission(String name, Collection<String> verbs, boolean groupPermission) { this.name = name; - this.verbs = unmodifiableCollection(new LinkedHashSet<>(verbs)); + this.verbs = unmodifiableSet(new LinkedHashSet<>(verbs)); this.groupPermission = groupPermission; } @@ -209,6 +210,6 @@ public class RepositoryPermission implements PermissionObject, Serializable */ public void setVerbs(Collection<String> verbs) { - this.verbs = verbs; + this.verbs = unmodifiableSet(new LinkedHashSet<>(verbs)); } } diff --git a/scm-core/src/test/java/sonia/scm/repository/RepositoryPermissionTest.java b/scm-core/src/test/java/sonia/scm/repository/RepositoryPermissionTest.java index 2e9383b2e2..d65358a66e 100644 --- a/scm-core/src/test/java/sonia/scm/repository/RepositoryPermissionTest.java +++ b/scm-core/src/test/java/sonia/scm/repository/RepositoryPermissionTest.java @@ -46,4 +46,13 @@ class RepositoryPermissionTest { assertThat(permission1).isNotEqualTo(permission2); } + + @Test + void shouldBeEqualWithRedundantVerbs() { + RepositoryPermission permission1 = new RepositoryPermission("name1", asList("one", "two"), false); + RepositoryPermission permission2 = new RepositoryPermission("name1", asList("one", "two"), false); + permission2.setVerbs(asList("one", "two", "two")); + + assertThat(permission1).isEqualTo(permission2); + } } From f93a35d97073bb70ff982a835be1d418b79aba30 Mon Sep 17 00:00:00 2001 From: Philipp Czora <philipp.czora@cloudogu.com> Date: Mon, 4 Feb 2019 16:26:00 +0000 Subject: [PATCH 084/112] Close branch feature/embedded_enricher From 9c3639409f13bebd999118abe97493e74f2b301a Mon Sep 17 00:00:00 2001 From: Mohamed Karray <mohamed.karray.sr@gmail.com> Date: Tue, 5 Feb 2019 08:45:03 +0100 Subject: [PATCH 085/112] fix redirect route --- scm-ui/src/containers/Main.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scm-ui/src/containers/Main.js b/scm-ui/src/containers/Main.js index a8b6c056b6..e963fb00f9 100644 --- a/scm-ui/src/containers/Main.js +++ b/scm-ui/src/containers/Main.js @@ -10,7 +10,7 @@ import Login from "../containers/Login"; import Logout from "../containers/Logout"; import { ProtectedRoute } from "@scm-manager/ui-components"; -import { ExtensionPoint } from "@scm-manager/ui-extensions"; +import {binder, ExtensionPoint } from "@scm-manager/ui-extensions"; import AddUser from "../users/containers/AddUser"; import SingleUser from "../users/containers/SingleUser"; @@ -32,15 +32,15 @@ type Props = { class Main extends React.Component<Props> { render() { const { authenticated, links } = this.props; + const redirectUrlFactory = binder.getExtension("main.redirect", this.props); + let url ="/repos"; + if (redirectUrlFactory){ + url = redirectUrlFactory(this.props); + } return ( <div className="main"> <Switch> - <ExtensionPoint - name="redirect-route" - props={{authenticated, links}} - > - <Redirect exact path="/" to="/repos"/> - </ExtensionPoint> + <Redirect exact path="/" to={url}/> <Route exact path="/login" component={Login} /> <Route path="/logout" component={Logout} /> <ProtectedRoute From 1af2035ffad04d29de87577fa25d0063c362b81c Mon Sep 17 00:00:00 2001 From: Mohamed Karray <mohamed.karray.sr@gmail.com> Date: Tue, 5 Feb 2019 09:45:09 +0100 Subject: [PATCH 086/112] merge --- .../DefaultChangesetToChangesetDtoMapper.java | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DefaultChangesetToChangesetDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DefaultChangesetToChangesetDtoMapper.java index 924209d3da..b04d45047a 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DefaultChangesetToChangesetDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DefaultChangesetToChangesetDtoMapper.java @@ -1,11 +1,11 @@ package sonia.scm.api.v2.resources; +import de.otto.edison.hal.Embedded; import de.otto.edison.hal.Links; -import org.mapstruct.AfterMapping; import org.mapstruct.Context; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.MappingTarget; +import org.mapstruct.ObjectFactory; import sonia.scm.repository.Branch; import sonia.scm.repository.Changeset; import sonia.scm.repository.Repository; @@ -19,11 +19,12 @@ import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; +import static de.otto.edison.hal.Embedded.embeddedBuilder; import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Links.linkingTo; @Mapper -public abstract class DefaultChangesetToChangesetDtoMapper extends LinkAppenderMapper implements InstantAttributeMapper , ChangesetToChangesetDtoMapper { +public abstract class DefaultChangesetToChangesetDtoMapper extends HalAppenderMapper implements InstantAttributeMapper, ChangesetToChangesetDtoMapper{ @Inject private RepositoryServiceFactory serviceFactory; @@ -31,7 +32,6 @@ public abstract class DefaultChangesetToChangesetDtoMapper extends LinkAppenderM @Inject private ResourceLinks resourceLinks; - @Inject private BranchCollectionToDtoMapper branchCollectionToDtoMapper; @@ -46,31 +46,34 @@ public abstract class DefaultChangesetToChangesetDtoMapper extends LinkAppenderM public abstract ChangesetDto map(Changeset changeset, @Context Repository repository); - @AfterMapping - void appendLinks(Changeset source, @MappingTarget ChangesetDto target, @Context Repository repository) { + @ObjectFactory + ChangesetDto createDto(@Context Repository repository, Changeset source) { String namespace = repository.getNamespace(); String name = repository.getName(); + Embedded.Builder embeddedBuilder = embeddedBuilder(); + try (RepositoryService repositoryService = serviceFactory.create(repository)) { if (repositoryService.isSupported(Command.TAGS)) { - target.withEmbedded("tags", tagCollectionToDtoMapper.getTagDtoList(namespace, name, + embeddedBuilder.with("tags", tagCollectionToDtoMapper.getTagDtoList(namespace, name, getListOfObjects(source.getTags(), tagName -> new Tag(tagName, source.getId())))); } if (repositoryService.isSupported(Command.BRANCHES)) { - target.withEmbedded("branches", branchCollectionToDtoMapper.getBranchDtoList(namespace, name, + embeddedBuilder.with("branches", branchCollectionToDtoMapper.getBranchDtoList(namespace, name, getListOfObjects(source.getBranches(), branchName -> new Branch(branchName, source.getId())))); } } - target.withEmbedded("parents", getListOfObjects(source.getParents(), parent -> changesetToParentDtoMapper.map(new Changeset(parent, 0L, null), repository))); + embeddedBuilder.with("parents", getListOfObjects(source.getParents(), parent -> changesetToParentDtoMapper.map(new Changeset(parent, 0L, null), repository))); Links.Builder linksBuilder = linkingTo() - .self(resourceLinks.changeset().self(repository.getNamespace(), repository.getName(), target.getId())) - .single(link("diff", resourceLinks.diff().self(namespace, name, target.getId()))) - .single(link("modifications", resourceLinks.modifications().self(namespace, name, target.getId()))); + .self(resourceLinks.changeset().self(repository.getNamespace(), repository.getName(), source.getId())) + .single(link("diff", resourceLinks.diff().self(namespace, name, source.getId()))) + .single(link("modifications", resourceLinks.modifications().self(namespace, name, source.getId()))); - appendLinks(new EdisonLinkAppender(linksBuilder), source, repository); - target.add(linksBuilder.build()); + applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), source, repository); + + return new ChangesetDto(linksBuilder.build(), embeddedBuilder.build()); } private <T> List<T> getListOfObjects(List<String> list, Function<String, T> mapFunction) { From 8d4f06d7b1b900ad76a819d17b6f6095bdb12721 Mon Sep 17 00:00:00 2001 From: Mohamed Karray <mohamed.karray.sr@gmail.com> Date: Tue, 5 Feb 2019 13:38:21 +0100 Subject: [PATCH 087/112] add the sources link to the changeset dto --- .../api/v2/resources/DefaultChangesetToChangesetDtoMapper.java | 1 + 1 file changed, 1 insertion(+) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DefaultChangesetToChangesetDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DefaultChangesetToChangesetDtoMapper.java index b04d45047a..479a43aef1 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DefaultChangesetToChangesetDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DefaultChangesetToChangesetDtoMapper.java @@ -68,6 +68,7 @@ public abstract class DefaultChangesetToChangesetDtoMapper extends HalAppenderMa Links.Builder linksBuilder = linkingTo() .self(resourceLinks.changeset().self(repository.getNamespace(), repository.getName(), source.getId())) .single(link("diff", resourceLinks.diff().self(namespace, name, source.getId()))) + .single(link("sources", resourceLinks.source().self(namespace, name, source.getId()))) .single(link("modifications", resourceLinks.modifications().self(namespace, name, source.getId()))); From 6415efe9bf32770e3546d512c143d093f8cd5521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= <rene.pfeuffer@cloudogu.com> Date: Tue, 5 Feb 2019 14:35:50 +0000 Subject: [PATCH 088/112] Close branch feature/move_changesetdto From 18b64ebde2e34b36f484de186a4c980bb9899547 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Tue, 5 Feb 2019 16:47:22 +0100 Subject: [PATCH 089/112] fix GZipResponseFilter b using the HttpServletRequest to check if the client supports gzip --- .../sonia/scm/filter/GZipResponseFilter.java | 19 ++++++++++++++----- .../scm/filter/GZipResponseFilterTest.java | 16 +++++++++++----- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/filter/GZipResponseFilter.java b/scm-core/src/main/java/sonia/scm/filter/GZipResponseFilter.java index 92dd033af0..ef0ec8a5ef 100644 --- a/scm-core/src/main/java/sonia/scm/filter/GZipResponseFilter.java +++ b/scm-core/src/main/java/sonia/scm/filter/GZipResponseFilter.java @@ -3,9 +3,11 @@ package sonia.scm.filter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.servlet.http.HttpServletRequest; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.ext.Provider; import javax.ws.rs.ext.WriterInterceptor; import javax.ws.rs.ext.WriterInterceptorContext; import java.io.IOException; @@ -13,14 +15,21 @@ import java.io.OutputStream; import java.util.Locale; import java.util.zip.GZIPOutputStream; -@Provider +@javax.ws.rs.ext.Provider public class GZipResponseFilter implements WriterInterceptor { private static final Logger LOG = LoggerFactory.getLogger(GZipResponseFilter.class); + private final Provider<HttpServletRequest> requestProvider; + + @Inject + public GZipResponseFilter(Provider<HttpServletRequest> requestProvider) { + this.requestProvider = requestProvider; + } + @Override public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { - if (isGZipSupported(context)) { + if (isGZipSupported()) { LOG.trace("compress output with gzip"); encodeWithGZip(context); } else { @@ -43,8 +52,8 @@ public class GZipResponseFilter implements WriterInterceptor { } } - private boolean isGZipSupported(WriterInterceptorContext context) { - Object encoding = context.getHeaders().getFirst(HttpHeaders.ACCEPT_ENCODING); + private boolean isGZipSupported() { + Object encoding = requestProvider.get().getHeader(HttpHeaders.ACCEPT_ENCODING); return encoding != null && encoding.toString().toLowerCase(Locale.ENGLISH).contains("gzip"); } } diff --git a/scm-core/src/test/java/sonia/scm/filter/GZipResponseFilterTest.java b/scm-core/src/test/java/sonia/scm/filter/GZipResponseFilterTest.java index b0a704c765..c3648fd4b8 100644 --- a/scm-core/src/test/java/sonia/scm/filter/GZipResponseFilterTest.java +++ b/scm-core/src/test/java/sonia/scm/filter/GZipResponseFilterTest.java @@ -1,5 +1,6 @@ package sonia.scm.filter; +import com.google.inject.util.Providers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -7,6 +8,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.WriterInterceptorContext; @@ -20,22 +22,25 @@ import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class GZipResponseFilterTest { + @Mock + private HttpServletRequest request; + @Mock private WriterInterceptorContext context; @Mock private MultivaluedMap<String,Object> headers; - private final GZipResponseFilter filter = new GZipResponseFilter(); + private GZipResponseFilter filter; @BeforeEach - void setUpContext() { - when(context.getHeaders()).thenReturn(headers); + void setupObjectUnderTest() { + filter = new GZipResponseFilter(Providers.of(request)); } @Test void shouldSkipGZipCompression() throws IOException { - when(headers.getFirst(HttpHeaders.ACCEPT_ENCODING)).thenReturn("deflate, br"); + when(request.getHeader(HttpHeaders.ACCEPT_ENCODING)).thenReturn("deflate, br"); filter.aroundWriteTo(context); @@ -60,7 +65,8 @@ class GZipResponseFilterTest { @BeforeEach void setUpContext() { - when(headers.getFirst(HttpHeaders.ACCEPT_ENCODING)).thenReturn("gzip, deflate, br"); + when(request.getHeader(HttpHeaders.ACCEPT_ENCODING)).thenReturn("gzip, deflate, br"); + when(context.getHeaders()).thenReturn(headers); when(context.getOutputStream()).thenReturn(new ByteArrayOutputStream()); } From 0a9abe629c89ce5e033f79cc7ddd972d782acacd Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Tue, 5 Feb 2019 16:47:46 +0100 Subject: [PATCH 090/112] name variable by role and not by type --- scm-core/src/main/java/sonia/scm/util/Comparables.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/util/Comparables.java b/scm-core/src/main/java/sonia/scm/util/Comparables.java index b760021c07..1fb0c5e358 100644 --- a/scm-core/src/main/java/sonia/scm/util/Comparables.java +++ b/scm-core/src/main/java/sonia/scm/util/Comparables.java @@ -53,11 +53,11 @@ public final class Comparables { private static PropertyDescriptor findPropertyDescriptor(String sortBy, BeanInfo info) { PropertyDescriptor[] propertyDescriptors = info.getPropertyDescriptors(); - Optional<PropertyDescriptor> optional = Arrays.stream(propertyDescriptors) + Optional<PropertyDescriptor> sortByPropertyDescriptor = Arrays.stream(propertyDescriptors) .filter(p -> p.getName().equals(sortBy)) .findFirst(); - return optional.orElseThrow(() -> new IllegalArgumentException("could not find property " + sortBy)); + return sortByPropertyDescriptor.orElseThrow(() -> new IllegalArgumentException("could not find property " + sortBy)); } private static <T> BeanInfo createBeanInfo(Class<T> type) { From 32aef466f5380d3e05306e33b616ac1355f091f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= <rene.pfeuffer@cloudogu.com> Date: Tue, 5 Feb 2019 20:47:19 +0000 Subject: [PATCH 091/112] Close branch feature/dependency_updates_and_lifecycle From 1c0f417f9af7b57e5f2d698abbd18c26f476fea6 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 6 Feb 2019 09:51:31 +0100 Subject: [PATCH 092/112] added extension point for default branch + renamed bindrepositorysetting --- .../src/main/js/RepositoryConfig.js | 1 + .../scm-git-plugin/src/main/js/index.js | 9 ++---- .../src/config/ConfigurationBinder.js | 2 +- scm-ui/src/repos/containers/EditRepo.js | 28 ++++++++++++++++++- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js b/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js index e34a0ef96f..80817664d5 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js +++ b/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js @@ -127,6 +127,7 @@ class RepositoryConfig extends React.Component<Props, State> { disabled={!this.state.selectedBranchName} /> </form> + <hr /> </> ); } else { diff --git a/scm-plugins/scm-git-plugin/src/main/js/index.js b/scm-plugins/scm-git-plugin/src/main/js/index.js index d3fe67476b..43e3950beb 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/index.js +++ b/scm-plugins/scm-git-plugin/src/main/js/index.js @@ -27,14 +27,9 @@ binder.bind( ); binder.bind("repos.repository-avatar", GitAvatar, gitPredicate); -cfgBinder.bindRepositorySub( - "/configuration", - "scm-git-plugin.repo-config.link", - "configuration", - RepositoryConfig -); -// global config +binder.bind("repo-config.route", RepositoryConfig, gitPredicate); +// global config cfgBinder.bindGlobal( "/git", "scm-git-plugin.config.link", diff --git a/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js b/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js index da1372c316..56964b016b 100644 --- a/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js +++ b/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js @@ -72,7 +72,7 @@ class ConfigurationBinder { binder.bind("repository.route", RepoRoute, repoPredicate); } - bindRepositorySub(to: string, labelI18nKey: string, linkName: string, RepositoryComponent: any) { + bindRepositorySetting(to: string, labelI18nKey: string, linkName: string, RepositoryComponent: any) { // create predicate based on the link name of the current repository route // if the linkname is not available, the navigation link and the route are not bound to the extension points diff --git a/scm-ui/src/repos/containers/EditRepo.js b/scm-ui/src/repos/containers/EditRepo.js index 072a45b670..a013a226c3 100644 --- a/scm-ui/src/repos/containers/EditRepo.js +++ b/scm-ui/src/repos/containers/EditRepo.js @@ -14,6 +14,7 @@ import { } from "../modules/repos"; import type { History } from "history"; import { ErrorNotification } from "@scm-manager/ui-components"; +import { ExtensionPoint } from "@scm-manager/ui-extensions"; type Props = { loading: boolean, @@ -25,7 +26,8 @@ type Props = { // context props repository: Repository, - history: History + history: History, + match: any }; class EditRepo extends React.Component<Props> { @@ -47,8 +49,27 @@ class EditRepo extends React.Component<Props> { this.props.deleteRepo(repository, this.deleted); }; + stripEndingSlash = (url: string) => { + if (url.endsWith("/")) { + return url.substring(0, url.length - 2); + } + return url; + }; + + matchedUrl = () => { + return this.stripEndingSlash(this.props.match.url); + }; + render() { const { loading, error, repository } = this.props; + + const url = this.matchedUrl(); + + const extensionProps = { + repository, + url + }; + return ( <div> <ErrorNotification error={error} /> @@ -60,6 +81,11 @@ class EditRepo extends React.Component<Props> { }} /> <hr /> + <ExtensionPoint + name="repo-config.route" + props={extensionProps} + renderAll={true} + /> <DeleteRepo repository={repository} delete={this.delete} /> </div> ); From ac6baa55eb18e45ad6fa557ee5fc45350f2f1b4c Mon Sep 17 00:00:00 2001 From: Mohamed Karray <mohamed.karray.sr@gmail.com> Date: Wed, 6 Feb 2019 09:02:03 +0000 Subject: [PATCH 093/112] Close branch feature/enhance_download_button From 0140c15216e096274ea72e9a9e8d3a8c5b7780d5 Mon Sep 17 00:00:00 2001 From: Mohamed Karray <mohamed.karray.sr@gmail.com> Date: Wed, 6 Feb 2019 09:04:04 +0000 Subject: [PATCH 094/112] Close branch bugfix/read_vcs_versions From 6f1615ef0a67db69f58961d8bde24fb44a77f04f Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 6 Feb 2019 10:42:25 +0100 Subject: [PATCH 095/112] added loading and error state --- scm-ui/src/groups/components/DeleteGroup.js | 26 +++++++++++++++++--- scm-ui/src/groups/containers/EditGroup.js | 12 +++------ scm-ui/src/repos/components/DeleteRepo.js | 27 ++++++++++++++++++--- scm-ui/src/users/components/DeleteUser.js | 26 +++++++++++++++++--- scm-ui/src/users/containers/EditUser.js | 4 +-- 5 files changed, 76 insertions(+), 19 deletions(-) diff --git a/scm-ui/src/groups/components/DeleteGroup.js b/scm-ui/src/groups/components/DeleteGroup.js index c32983ff94..5604685ae5 100644 --- a/scm-ui/src/groups/components/DeleteGroup.js +++ b/scm-ui/src/groups/components/DeleteGroup.js @@ -2,9 +2,18 @@ import React from "react"; import { translate } from "react-i18next"; import type { Group } from "@scm-manager/ui-types"; -import { Subtitle, DeleteButton, confirmAlert } from "@scm-manager/ui-components"; +import { + Subtitle, + DeleteButton, + confirmAlert +} from "@scm-manager/ui-components"; +import { getDeleteGroupFailure, isDeleteGroupPending } from "../modules/groups"; +import { connect } from "react-redux"; +import { ErrorNotification } from "@scm-manager/ui-components"; type Props = { + loading: boolean, + error: Error, group: Group, confirmDialog?: boolean, deleteGroup: (group: Group) => void, @@ -43,7 +52,7 @@ export class DeleteGroup extends React.Component<Props> { }; render() { - const { confirmDialog, t } = this.props; + const { loading, error, confirmDialog, t } = this.props; const action = confirmDialog ? this.confirmDelete : this.deleteGroup; if (!this.isDeletable()) { @@ -53,11 +62,13 @@ export class DeleteGroup extends React.Component<Props> { return ( <> <Subtitle subtitle={t("deleteGroup.subtitle")} /> + <ErrorNotification error={error} /> <div className="columns"> <div className="column"> <DeleteButton label={t("deleteGroup.button")} action={action} + loading={loading} /> </div> </div> @@ -66,4 +77,13 @@ export class DeleteGroup extends React.Component<Props> { } } -export default translate("groups")(DeleteGroup); +const mapStateToProps = (state, ownProps) => { + const loading = isDeleteGroupPending(state, ownProps.group.name); + const error = getDeleteGroupFailure(state, ownProps.group.name); + return { + loading, + error + }; +}; + +export default connect(mapStateToProps)(translate("groups")(DeleteGroup)); diff --git a/scm-ui/src/groups/containers/EditGroup.js b/scm-ui/src/groups/containers/EditGroup.js index 241a61356a..3a83728dde 100644 --- a/scm-ui/src/groups/containers/EditGroup.js +++ b/scm-ui/src/groups/containers/EditGroup.js @@ -7,8 +7,6 @@ import { deleteGroup, getModifyGroupFailure, isModifyGroupPending, - getDeleteGroupFailure, - isDeleteGroupPending, modifyGroupReset } from "../modules/groups"; import type { History } from "history"; @@ -67,7 +65,7 @@ class EditGroup extends React.Component<Props> { }; render() { - const { group, loading, error } = this.props; + const { loading, error, group } = this.props; return ( <div> <ErrorNotification error={error} /> @@ -80,17 +78,15 @@ class EditGroup extends React.Component<Props> { loadUserSuggestions={this.loadUserAutocompletion} /> <hr /> - <DeleteGroup - group={group} - deleteGroup={this.deleteGroup} /> + <DeleteGroup group={group} deleteGroup={this.deleteGroup} /> </div> ); } } const mapStateToProps = (state, ownProps) => { - const loading = isModifyGroupPending(state, ownProps.group.name) || isDeleteGroupPending(state, ownProps.group.name); - const error = getModifyGroupFailure(state, ownProps.group.name) || getDeleteGroupFailure(state, ownProps.group.name); + const loading = isModifyGroupPending(state, ownProps.group.name); + const error = getModifyGroupFailure(state, ownProps.group.name); const autocompleteLink = getUserAutoCompleteLink(state); return { loading, diff --git a/scm-ui/src/repos/components/DeleteRepo.js b/scm-ui/src/repos/components/DeleteRepo.js index 3ba7b08d48..4d1b9f3c7c 100644 --- a/scm-ui/src/repos/components/DeleteRepo.js +++ b/scm-ui/src/repos/components/DeleteRepo.js @@ -2,9 +2,18 @@ import React from "react"; import { translate } from "react-i18next"; import type { Repository } from "@scm-manager/ui-types"; -import { Subtitle, DeleteButton, confirmAlert } from "@scm-manager/ui-components"; +import { + Subtitle, + DeleteButton, + confirmAlert +} from "@scm-manager/ui-components"; +import { getDeleteRepoFailure, isDeleteRepoPending } from "../modules/repos"; +import { connect } from "react-redux"; +import { ErrorNotification } from "@scm-manager/ui-components"; type Props = { + loading: boolean, + error: Error, repository: Repository, confirmDialog?: boolean, @@ -47,7 +56,7 @@ class DeleteRepo extends React.Component<Props> { }; render() { - const { confirmDialog, t } = this.props; + const { loading, error, confirmDialog, t } = this.props; const action = confirmDialog ? this.confirmDelete : this.deleteRepo; if (!this.isDeletable()) { @@ -57,11 +66,13 @@ class DeleteRepo extends React.Component<Props> { return ( <> <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> @@ -70,4 +81,14 @@ class DeleteRepo extends React.Component<Props> { } } -export default translate("repos")(DeleteRepo); +const mapStateToProps = (state, ownProps) => { + const { namespace, name } = ownProps.repository; + const loading = isDeleteRepoPending(state, namespace, name); + const error = getDeleteRepoFailure(state, namespace, name); + return { + loading, + error + }; +}; + +export default connect(mapStateToProps)(translate("repos")(DeleteRepo)); diff --git a/scm-ui/src/users/components/DeleteUser.js b/scm-ui/src/users/components/DeleteUser.js index b8523a375e..91868e44fb 100644 --- a/scm-ui/src/users/components/DeleteUser.js +++ b/scm-ui/src/users/components/DeleteUser.js @@ -2,9 +2,18 @@ import React from "react"; import { translate } from "react-i18next"; import type { User } from "@scm-manager/ui-types"; -import { Subtitle, DeleteButton, confirmAlert } from "@scm-manager/ui-components"; +import { + Subtitle, + DeleteButton, + confirmAlert +} from "@scm-manager/ui-components"; +import { getDeleteUserFailure, isDeleteUserPending } from "../modules/users"; +import { connect } from "react-redux"; +import { ErrorNotification } from "@scm-manager/ui-components"; type Props = { + loading: boolean, + error: Error, user: User, confirmDialog?: boolean, @@ -47,7 +56,7 @@ class DeleteUser extends React.Component<Props> { }; render() { - const { confirmDialog, t } = this.props; + const { loading, error, confirmDialog, t } = this.props; const action = confirmDialog ? this.confirmDelete : this.deleteUser; if (!this.isDeletable()) { @@ -57,11 +66,13 @@ class DeleteUser extends React.Component<Props> { return ( <> <Subtitle subtitle={t("deleteUser.subtitle")} /> + <ErrorNotification error={error} /> <div className="columns"> <div className="column"> <DeleteButton label={t("deleteUser.button")} action={action} + loading={loading} /> </div> </div> @@ -70,4 +81,13 @@ class DeleteUser extends React.Component<Props> { } } -export default translate("users")(DeleteUser); +const mapStateToProps = (state, ownProps) => { + const loading = isDeleteUserPending(state, ownProps.user.name); + const error = getDeleteUserFailure(state, ownProps.user.name); + return { + loading, + error + }; +}; + +export default connect(mapStateToProps)(translate("users")(DeleteUser)); diff --git a/scm-ui/src/users/containers/EditUser.js b/scm-ui/src/users/containers/EditUser.js index 6b061c48bf..4198c97447 100644 --- a/scm-ui/src/users/containers/EditUser.js +++ b/scm-ui/src/users/containers/EditUser.js @@ -71,8 +71,8 @@ class EditUser extends React.Component<Props> { } const mapStateToProps = (state, ownProps) => { - const loading = isModifyUserPending(state, ownProps.user.name) || isDeleteUserPending(state, ownProps.user.name); - const error = getModifyUserFailure(state, ownProps.user.name) || getDeleteUserFailure(state, ownProps.user.name); + const loading = isModifyUserPending(state, ownProps.user.name); + const error = getModifyUserFailure(state, ownProps.user.name); return { loading, error From 2a830e4bc4aaaaaa0fc8ec77a79ec4afe44f3fb3 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 6 Feb 2019 11:17:06 +0100 Subject: [PATCH 096/112] fixed deleterepo test --- scm-ui/src/repos/components/DeleteRepo.js | 4 +-- .../src/repos/components/DeleteRepo.test.js | 25 +++++++++++++------ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/scm-ui/src/repos/components/DeleteRepo.js b/scm-ui/src/repos/components/DeleteRepo.js index 4d1b9f3c7c..cca0944f1b 100644 --- a/scm-ui/src/repos/components/DeleteRepo.js +++ b/scm-ui/src/repos/components/DeleteRepo.js @@ -5,11 +5,11 @@ import type { Repository } from "@scm-manager/ui-types"; import { Subtitle, DeleteButton, - confirmAlert + confirmAlert, + ErrorNotification } from "@scm-manager/ui-components"; import { getDeleteRepoFailure, isDeleteRepoPending } from "../modules/repos"; import { connect } from "react-redux"; -import { ErrorNotification } from "@scm-manager/ui-components"; type Props = { loading: boolean, diff --git a/scm-ui/src/repos/components/DeleteRepo.test.js b/scm-ui/src/repos/components/DeleteRepo.test.js index 136cec6c03..3ef7368508 100644 --- a/scm-ui/src/repos/components/DeleteRepo.test.js +++ b/scm-ui/src/repos/components/DeleteRepo.test.js @@ -1,30 +1,40 @@ import React from "react"; -import { mount, shallow } from "enzyme"; +import { mount } from "enzyme"; import ReactRouterEnzymeContext from "react-router-enzyme-context"; +import configureStore from "redux-mock-store"; import "../../tests/enzyme"; import "../../tests/i18n"; import DeleteRepo from "./DeleteRepo"; import { confirmAlert } from "@scm-manager/ui-components"; + jest.mock("@scm-manager/ui-components", () => ({ confirmAlert: jest.fn(), Subtitle: require.requireActual("@scm-manager/ui-components").Subtitle, - DeleteButton: require.requireActual("@scm-manager/ui-components").DeleteButton + DeleteButton: require.requireActual("@scm-manager/ui-components").DeleteButton, + ErrorNotification: ({err}) => err ? err.message : null })); const options = new ReactRouterEnzymeContext(); describe("DeleteRepo", () => { + + let store; + + beforeEach(() => { + store = configureStore()({}); + }); + it("should render nothing, if the delete link is missing", () => { const repository = { _links: {} }; - const navLink = shallow( - <DeleteRepo repository={repository} delete={() => {}} /> + const navLink = mount( + <DeleteRepo repository={repository} delete={() => {}} store={store} /> ); - expect(navLink.text()).toBe(""); + expect(navLink.text()).toBeNull(); }); it("should render the navLink", () => { @@ -37,7 +47,7 @@ describe("DeleteRepo", () => { }; const navLink = mount( - <DeleteRepo repository={repository} delete={() => {}} />, + <DeleteRepo repository={repository} delete={() => {}} store={store} />, options.get() ); expect(navLink.text()).not.toBe(""); @@ -53,7 +63,7 @@ describe("DeleteRepo", () => { }; const navLink = mount( - <DeleteRepo repository={repository} delete={() => {}} />, + <DeleteRepo repository={repository} delete={() => {}} store={store} />, options.get() ); navLink.find("button").simulate("click"); @@ -80,6 +90,7 @@ describe("DeleteRepo", () => { repository={repository} confirmDialog={false} delete={capture} + store={store} />, options.get() ); From 1c03c91e21f06ab656990d188db20d993b7871be Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 6 Feb 2019 11:44:03 +0100 Subject: [PATCH 097/112] moved edit and delete funcs --- .../{components => containers}/DeleteGroup.js | 33 +++++++++++++++---- .../DeleteGroup.test.js | 0 scm-ui/src/groups/containers/EditGroup.js | 17 ++-------- .../{components => containers}/DeleteRepo.js | 32 ++++++++++++++---- .../DeleteRepo.test.js | 12 ++----- scm-ui/src/repos/containers/EditRepo.js | 17 ++-------- .../{components => containers}/DeleteUser.js | 31 ++++++++++++----- .../DeleteUser.test.js | 0 scm-ui/src/users/containers/EditUser.js | 19 ++--------- 9 files changed, 84 insertions(+), 77 deletions(-) rename scm-ui/src/groups/{components => containers}/DeleteGroup.js (71%) rename scm-ui/src/groups/{components => containers}/DeleteGroup.test.js (100%) rename scm-ui/src/repos/{components => containers}/DeleteRepo.js (74%) rename scm-ui/src/repos/{components => containers}/DeleteRepo.test.js (84%) rename scm-ui/src/users/{components => containers}/DeleteUser.js (72%) rename scm-ui/src/users/{components => containers}/DeleteUser.test.js (100%) diff --git a/scm-ui/src/groups/components/DeleteGroup.js b/scm-ui/src/groups/containers/DeleteGroup.js similarity index 71% rename from scm-ui/src/groups/components/DeleteGroup.js rename to scm-ui/src/groups/containers/DeleteGroup.js index 5604685ae5..b9a453618a 100644 --- a/scm-ui/src/groups/components/DeleteGroup.js +++ b/scm-ui/src/groups/containers/DeleteGroup.js @@ -5,18 +5,27 @@ import type { Group } from "@scm-manager/ui-types"; import { Subtitle, DeleteButton, - confirmAlert + confirmAlert, + ErrorNotification } from "@scm-manager/ui-components"; -import { getDeleteGroupFailure, isDeleteGroupPending } from "../modules/groups"; +import { + deleteGroup, + getDeleteGroupFailure, + isDeleteGroupPending, +} from "../modules/groups"; import { connect } from "react-redux"; -import { ErrorNotification } from "@scm-manager/ui-components"; +import {withRouter} from "react-router-dom"; +import type {History} from "history"; type Props = { loading: boolean, error: Error, group: Group, confirmDialog?: boolean, - deleteGroup: (group: Group) => void, + deleteGroup: (group: Group, callback?: () => void) => void, + + // context props + history: History, t: string => string }; @@ -26,7 +35,11 @@ export class DeleteGroup extends React.Component<Props> { }; deleteGroup = () => { - this.props.deleteGroup(this.props.group); + this.props.deleteGroup(this.props.group, this.groupDeleted); + }; + + groupDeleted = () => { + this.props.history.push("/groups"); }; confirmDelete = () => { @@ -86,4 +99,12 @@ const mapStateToProps = (state, ownProps) => { }; }; -export default connect(mapStateToProps)(translate("groups")(DeleteGroup)); +const mapDispatchToProps = dispatch => { + return { + deleteGroup: (group: Group, callback?: () => void) => { + dispatch(deleteGroup(group, callback)); + } + }; +}; + +export default connect(mapStateToProps, mapDispatchToProps)(withRouter(translate("groups")(DeleteGroup))); diff --git a/scm-ui/src/groups/components/DeleteGroup.test.js b/scm-ui/src/groups/containers/DeleteGroup.test.js similarity index 100% rename from scm-ui/src/groups/components/DeleteGroup.test.js rename to scm-ui/src/groups/containers/DeleteGroup.test.js diff --git a/scm-ui/src/groups/containers/EditGroup.js b/scm-ui/src/groups/containers/EditGroup.js index 3a83728dde..a1f9f31e46 100644 --- a/scm-ui/src/groups/containers/EditGroup.js +++ b/scm-ui/src/groups/containers/EditGroup.js @@ -4,7 +4,6 @@ import { connect } from "react-redux"; import GroupForm from "../components/GroupForm"; import { modifyGroup, - deleteGroup, getModifyGroupFailure, isModifyGroupPending, modifyGroupReset @@ -14,14 +13,13 @@ import { withRouter } from "react-router-dom"; import type { Group } from "@scm-manager/ui-types"; import { ErrorNotification } from "@scm-manager/ui-components"; import { getUserAutoCompleteLink } from "../../modules/indexResource"; -import DeleteGroup from "../components/DeleteGroup"; +import DeleteGroup from "./DeleteGroup"; type Props = { group: Group, fetchGroup: (name: string) => void, modifyGroup: (group: Group, callback?: () => void) => void, modifyGroupReset: Group => void, - deleteGroup: (group: Group, callback?: () => void) => void, autocompleteLink: string, history: History, loading?: boolean, @@ -42,14 +40,6 @@ class EditGroup extends React.Component<Props> { this.props.modifyGroup(group, this.groupModified(group)); }; - deleteGroup = (group: Group) => { - this.props.deleteGroup(group, this.groupDeleted); - }; - - groupDeleted = () => { - this.props.history.push("/groups"); - }; - loadUserAutocompletion = (inputValue: string) => { const url = this.props.autocompleteLink + "?q="; return fetch(url + inputValue) @@ -78,7 +68,7 @@ class EditGroup extends React.Component<Props> { loadUserSuggestions={this.loadUserAutocompletion} /> <hr /> - <DeleteGroup group={group} deleteGroup={this.deleteGroup} /> + <DeleteGroup group={group} /> </div> ); } @@ -102,9 +92,6 @@ const mapDispatchToProps = dispatch => { }, modifyGroupReset: (group: Group) => { dispatch(modifyGroupReset(group)); - }, - deleteGroup: (group: Group, callback?: () => void) => { - dispatch(deleteGroup(group, callback)); } }; }; diff --git a/scm-ui/src/repos/components/DeleteRepo.js b/scm-ui/src/repos/containers/DeleteRepo.js similarity index 74% rename from scm-ui/src/repos/components/DeleteRepo.js rename to scm-ui/src/repos/containers/DeleteRepo.js index cca0944f1b..9875f84aaa 100644 --- a/scm-ui/src/repos/components/DeleteRepo.js +++ b/scm-ui/src/repos/containers/DeleteRepo.js @@ -8,19 +8,24 @@ import { confirmAlert, ErrorNotification } from "@scm-manager/ui-components"; -import { getDeleteRepoFailure, isDeleteRepoPending } from "../modules/repos"; +import { + deleteRepo, + getDeleteRepoFailure, + isDeleteRepoPending, +} from "../modules/repos"; import { connect } from "react-redux"; +import {withRouter} from "react-router-dom"; +import type {History} from "history"; type Props = { loading: boolean, error: Error, repository: Repository, confirmDialog?: boolean, - - // dispatcher functions - delete: Repository => void, + deleteRepo: (Repository, () => void) => void, // context props + history: History, t: string => string }; @@ -29,8 +34,12 @@ class DeleteRepo extends React.Component<Props> { confirmDialog: true }; + deleted = () => { + this.props.history.push("/repos"); + }; + deleteRepo = () => { - this.props.delete(this.props.repository); + this.props.deleteRepo(this.props.repository, this.deleted); }; confirmDelete = () => { @@ -91,4 +100,15 @@ const mapStateToProps = (state, ownProps) => { }; }; -export default connect(mapStateToProps)(translate("repos")(DeleteRepo)); +const mapDispatchToProps = dispatch => { + return { + deleteRepo: (repo: Repository, callback: () => void) => { + dispatch(deleteRepo(repo, callback)); + } + }; +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(withRouter(translate("repos")(DeleteRepo))); diff --git a/scm-ui/src/repos/components/DeleteRepo.test.js b/scm-ui/src/repos/containers/DeleteRepo.test.js similarity index 84% rename from scm-ui/src/repos/components/DeleteRepo.test.js rename to scm-ui/src/repos/containers/DeleteRepo.test.js index 3ef7368508..5b37e63763 100644 --- a/scm-ui/src/repos/components/DeleteRepo.test.js +++ b/scm-ui/src/repos/containers/DeleteRepo.test.js @@ -32,7 +32,7 @@ describe("DeleteRepo", () => { }; const navLink = mount( - <DeleteRepo repository={repository} delete={() => {}} store={store} /> + <DeleteRepo repository={repository} store={store} /> ); expect(navLink.text()).toBeNull(); }); @@ -47,7 +47,7 @@ describe("DeleteRepo", () => { }; const navLink = mount( - <DeleteRepo repository={repository} delete={() => {}} store={store} />, + <DeleteRepo repository={repository} store={store} />, options.get() ); expect(navLink.text()).not.toBe(""); @@ -63,7 +63,7 @@ describe("DeleteRepo", () => { }; const navLink = mount( - <DeleteRepo repository={repository} delete={() => {}} store={store} />, + <DeleteRepo repository={repository} store={store} />, options.get() ); navLink.find("button").simulate("click"); @@ -80,16 +80,10 @@ describe("DeleteRepo", () => { } }; - let calledUrl = null; - function capture(repository) { - calledUrl = repository._links.delete.href; - } - const navLink = mount( <DeleteRepo repository={repository} confirmDialog={false} - delete={capture} store={store} />, options.get() diff --git a/scm-ui/src/repos/containers/EditRepo.js b/scm-ui/src/repos/containers/EditRepo.js index a013a226c3..2e5bb69172 100644 --- a/scm-ui/src/repos/containers/EditRepo.js +++ b/scm-ui/src/repos/containers/EditRepo.js @@ -3,11 +3,10 @@ import React from "react"; import { connect } from "react-redux"; import { withRouter } from "react-router-dom"; import RepositoryForm from "../components/form"; -import DeleteRepo from "../components/DeleteRepo"; +import DeleteRepo from "./DeleteRepo"; import type { Repository } from "@scm-manager/ui-types"; import { modifyRepo, - deleteRepo, isModifyRepoPending, getModifyRepoFailure, modifyRepoReset @@ -22,7 +21,6 @@ type Props = { modifyRepo: (Repository, () => void) => void, modifyRepoReset: Repository => void, - deleteRepo: (Repository, () => void) => void, // context props repository: Repository, @@ -41,14 +39,6 @@ class EditRepo extends React.Component<Props> { history.push(`/repo/${repository.namespace}/${repository.name}`); }; - deleted = () => { - this.props.history.push("/repos"); - }; - - delete = (repository: Repository) => { - this.props.deleteRepo(repository, this.deleted); - }; - stripEndingSlash = (url: string) => { if (url.endsWith("/")) { return url.substring(0, url.length - 2); @@ -86,7 +76,7 @@ class EditRepo extends React.Component<Props> { props={extensionProps} renderAll={true} /> - <DeleteRepo repository={repository} delete={this.delete} /> + <DeleteRepo repository={repository} /> </div> ); } @@ -109,9 +99,6 @@ const mapDispatchToProps = dispatch => { }, modifyRepoReset: (repo: Repository) => { dispatch(modifyRepoReset(repo)); - }, - deleteRepo: (repo: Repository, callback: () => void) => { - dispatch(deleteRepo(repo, callback)); } }; }; diff --git a/scm-ui/src/users/components/DeleteUser.js b/scm-ui/src/users/containers/DeleteUser.js similarity index 72% rename from scm-ui/src/users/components/DeleteUser.js rename to scm-ui/src/users/containers/DeleteUser.js index 91868e44fb..44ad087fe8 100644 --- a/scm-ui/src/users/components/DeleteUser.js +++ b/scm-ui/src/users/containers/DeleteUser.js @@ -5,22 +5,23 @@ import type { User } from "@scm-manager/ui-types"; import { Subtitle, DeleteButton, - confirmAlert + confirmAlert, + ErrorNotification } from "@scm-manager/ui-components"; -import { getDeleteUserFailure, isDeleteUserPending } from "../modules/users"; +import {deleteUser, getDeleteUserFailure, isDeleteUserPending} from "../modules/users"; import { connect } from "react-redux"; -import { ErrorNotification } from "@scm-manager/ui-components"; +import {withRouter} from "react-router-dom"; +import type {History} from "history"; type Props = { loading: boolean, error: Error, user: User, confirmDialog?: boolean, + deleteUser: (user: User, callback?: () => void) => void, - // dispatcher functions - deleteUser: (user: User) => void, - - // context objects + // context props + history: History, t: string => string }; @@ -29,8 +30,12 @@ class DeleteUser extends React.Component<Props> { confirmDialog: true }; + userDeleted = () => { + this.props.history.push("/users"); + }; + deleteUser = () => { - this.props.deleteUser(this.props.user); + this.props.deleteUser(this.props.user, this.userDeleted); }; confirmDelete = () => { @@ -90,4 +95,12 @@ const mapStateToProps = (state, ownProps) => { }; }; -export default connect(mapStateToProps)(translate("users")(DeleteUser)); +const mapDispatchToProps = dispatch => { + return { + deleteUser: (user: User, callback?: () => void) => { + dispatch(deleteUser(user, callback)); + } + }; +}; + +export default connect(mapStateToProps, mapDispatchToProps)(withRouter(translate("users")(DeleteUser))); diff --git a/scm-ui/src/users/components/DeleteUser.test.js b/scm-ui/src/users/containers/DeleteUser.test.js similarity index 100% rename from scm-ui/src/users/components/DeleteUser.test.js rename to scm-ui/src/users/containers/DeleteUser.test.js diff --git a/scm-ui/src/users/containers/EditUser.js b/scm-ui/src/users/containers/EditUser.js index 4198c97447..a0263fb9b4 100644 --- a/scm-ui/src/users/containers/EditUser.js +++ b/scm-ui/src/users/containers/EditUser.js @@ -3,16 +3,13 @@ import React from "react"; import { connect } from "react-redux"; import { withRouter } from "react-router-dom"; import UserForm from "../components/UserForm"; -import DeleteUser from "../components/DeleteUser"; +import DeleteUser from "./DeleteUser"; import type { User } from "@scm-manager/ui-types"; import { modifyUser, - deleteUser, isModifyUserPending, getModifyUserFailure, modifyUserReset, - isDeleteUserPending, - getDeleteUserFailure } from "../modules/users"; import type { History } from "history"; import { ErrorNotification } from "@scm-manager/ui-components"; @@ -24,7 +21,6 @@ type Props = { // dispatch functions modifyUser: (user: User, callback?: () => void) => void, modifyUserReset: User => void, - deleteUser: (user: User, callback?: () => void) => void, // context objects user: User, @@ -45,14 +41,6 @@ class EditUser extends React.Component<Props> { this.props.modifyUser(user, this.userModified(user)); }; - userDeleted = () => { - this.props.history.push("/users"); - }; - - deleteUser = (user: User) => { - this.props.deleteUser(user, this.userDeleted); - }; - render() { const { user, loading, error } = this.props; return ( @@ -64,7 +52,7 @@ class EditUser extends React.Component<Props> { loading={loading} /> <hr /> - <DeleteUser user={user} deleteUser={this.deleteUser} /> + <DeleteUser user={user} /> </div> ); } @@ -86,9 +74,6 @@ const mapDispatchToProps = dispatch => { }, modifyUserReset: (user: User) => { dispatch(modifyUserReset(user)); - }, - deleteUser: (user: User, callback?: () => void) => { - dispatch(deleteUser(user, callback)); } }; }; From 6231199862f433bc5d9c88f0701445582cc6d164 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 6 Feb 2019 12:10:06 +0100 Subject: [PATCH 098/112] styling --- scm-ui/src/groups/containers/DeleteGroup.js | 11 +++++++---- scm-ui/src/repos/containers/DeleteRepo.js | 6 +++--- scm-ui/src/users/containers/DeleteUser.js | 15 +++++++++++---- scm-ui/src/users/containers/EditUser.js | 2 +- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/scm-ui/src/groups/containers/DeleteGroup.js b/scm-ui/src/groups/containers/DeleteGroup.js index b9a453618a..d497e4434d 100644 --- a/scm-ui/src/groups/containers/DeleteGroup.js +++ b/scm-ui/src/groups/containers/DeleteGroup.js @@ -11,11 +11,11 @@ import { import { deleteGroup, getDeleteGroupFailure, - isDeleteGroupPending, + isDeleteGroupPending } from "../modules/groups"; import { connect } from "react-redux"; -import {withRouter} from "react-router-dom"; -import type {History} from "history"; +import { withRouter } from "react-router-dom"; +import type { History } from "history"; type Props = { loading: boolean, @@ -107,4 +107,7 @@ const mapDispatchToProps = dispatch => { }; }; -export default connect(mapStateToProps, mapDispatchToProps)(withRouter(translate("groups")(DeleteGroup))); +export default connect( + mapStateToProps, + mapDispatchToProps +)(withRouter(translate("groups")(DeleteGroup))); diff --git a/scm-ui/src/repos/containers/DeleteRepo.js b/scm-ui/src/repos/containers/DeleteRepo.js index 9875f84aaa..b621a1998b 100644 --- a/scm-ui/src/repos/containers/DeleteRepo.js +++ b/scm-ui/src/repos/containers/DeleteRepo.js @@ -11,11 +11,11 @@ import { import { deleteRepo, getDeleteRepoFailure, - isDeleteRepoPending, + isDeleteRepoPending } from "../modules/repos"; import { connect } from "react-redux"; -import {withRouter} from "react-router-dom"; -import type {History} from "history"; +import { withRouter } from "react-router-dom"; +import type { History } from "history"; type Props = { loading: boolean, diff --git a/scm-ui/src/users/containers/DeleteUser.js b/scm-ui/src/users/containers/DeleteUser.js index 44ad087fe8..b8b42fd9e8 100644 --- a/scm-ui/src/users/containers/DeleteUser.js +++ b/scm-ui/src/users/containers/DeleteUser.js @@ -8,10 +8,14 @@ import { confirmAlert, ErrorNotification } from "@scm-manager/ui-components"; -import {deleteUser, getDeleteUserFailure, isDeleteUserPending} from "../modules/users"; +import { + deleteUser, + getDeleteUserFailure, + isDeleteUserPending +} from "../modules/users"; import { connect } from "react-redux"; -import {withRouter} from "react-router-dom"; -import type {History} from "history"; +import { withRouter } from "react-router-dom"; +import type { History } from "history"; type Props = { loading: boolean, @@ -103,4 +107,7 @@ const mapDispatchToProps = dispatch => { }; }; -export default connect(mapStateToProps, mapDispatchToProps)(withRouter(translate("users")(DeleteUser))); +export default connect( + mapStateToProps, + mapDispatchToProps +)(withRouter(translate("users")(DeleteUser))); diff --git a/scm-ui/src/users/containers/EditUser.js b/scm-ui/src/users/containers/EditUser.js index a0263fb9b4..942d5182e7 100644 --- a/scm-ui/src/users/containers/EditUser.js +++ b/scm-ui/src/users/containers/EditUser.js @@ -9,7 +9,7 @@ import { modifyUser, isModifyUserPending, getModifyUserFailure, - modifyUserReset, + modifyUserReset } from "../modules/users"; import type { History } from "history"; import { ErrorNotification } from "@scm-manager/ui-components"; From 717ddda260ddf12aa696c26ac69991d938091b4f Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 6 Feb 2019 13:10:30 +0100 Subject: [PATCH 099/112] changed repo overview settings link --- scm-ui/src/repos/components/list/RepositoryEntry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-ui/src/repos/components/list/RepositoryEntry.js b/scm-ui/src/repos/components/list/RepositoryEntry.js index 28a6fe1ad3..b8b03a3523 100644 --- a/scm-ui/src/repos/components/list/RepositoryEntry.js +++ b/scm-ui/src/repos/components/list/RepositoryEntry.js @@ -64,7 +64,7 @@ class RepositoryEntry extends React.Component<Props> { return ( <RepositoryEntryLink iconClass="fa-cog fa-lg" - to={repositoryLink + "/edit"} + to={repositoryLink + "/settings/general"} /> ); } From 8e2a6276366ebbd1047e1f13b778dad291711273 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 6 Feb 2019 13:41:48 +0100 Subject: [PATCH 100/112] fixed tests --- .../src/groups/containers/DeleteGroup.test.js | 34 ++++++++++++------- .../src/repos/containers/DeleteRepo.test.js | 7 ++-- .../src/users/containers/DeleteUser.test.js | 34 ++++++++++++------- 3 files changed, 47 insertions(+), 28 deletions(-) diff --git a/scm-ui/src/groups/containers/DeleteGroup.test.js b/scm-ui/src/groups/containers/DeleteGroup.test.js index 043724d5f7..4c38623604 100644 --- a/scm-ui/src/groups/containers/DeleteGroup.test.js +++ b/scm-ui/src/groups/containers/DeleteGroup.test.js @@ -1,30 +1,41 @@ import React from "react"; -import { mount, shallow } from "enzyme"; +import { mount } from "enzyme"; import ReactRouterEnzymeContext from "react-router-enzyme-context"; +import configureStore from "redux-mock-store"; import "../../tests/enzyme"; import "../../tests/i18n"; import DeleteGroup from "./DeleteGroup"; import { confirmAlert } from "@scm-manager/ui-components"; + jest.mock("@scm-manager/ui-components", () => ({ confirmAlert: jest.fn(), Subtitle: require.requireActual("@scm-manager/ui-components").Subtitle, - DeleteButton: require.requireActual("@scm-manager/ui-components").DeleteButton + DeleteButton: require.requireActual("@scm-manager/ui-components").DeleteButton, + ErrorNotification: ({err}) => err ? err.message : null })); const options = new ReactRouterEnzymeContext(); describe("DeleteGroupNavLink", () => { + + let store; + + beforeEach(() => { + store = configureStore()({}); + }); + it("should render nothing, if the delete link is missing", () => { const group = { _links: {} }; - const navLink = shallow( - <DeleteGroup group={group} deleteGroup={() => {}} /> + const navLink = mount( + <DeleteGroup group={group} store={store} />, + options.get() ); - expect(navLink.text()).toBe(""); + expect(navLink.text()).toBeNull(); }); it("should render the navLink", () => { @@ -37,7 +48,7 @@ describe("DeleteGroupNavLink", () => { }; const navLink = mount( - <DeleteGroup group={group} deleteGroup={() => {}} />, + <DeleteGroup group={group} store={store} />, options.get() ); expect(navLink.text()).not.toBe(""); @@ -53,7 +64,7 @@ describe("DeleteGroupNavLink", () => { }; const navLink = mount( - <DeleteGroup group={group} deleteGroup={() => {}} />, + <DeleteGroup group={group} store={store} />, options.get() ); navLink.find("button").simulate("click"); @@ -70,21 +81,18 @@ describe("DeleteGroupNavLink", () => { } }; - let calledUrl = null; - function capture(group) { - calledUrl = group._links.delete.href; - } + store.dispatch = jest.fn(); const navLink = mount( <DeleteGroup group={group} confirmDialog={false} - deleteGroup={capture} + store={store} />, options.get() ); navLink.find("button").simulate("click"); - expect(calledUrl).toBe("/groups"); + expect(store.dispatch.mock.calls.length).toBe(1); }); }); diff --git a/scm-ui/src/repos/containers/DeleteRepo.test.js b/scm-ui/src/repos/containers/DeleteRepo.test.js index 5b37e63763..4f3ebd1049 100644 --- a/scm-ui/src/repos/containers/DeleteRepo.test.js +++ b/scm-ui/src/repos/containers/DeleteRepo.test.js @@ -32,7 +32,8 @@ describe("DeleteRepo", () => { }; const navLink = mount( - <DeleteRepo repository={repository} store={store} /> + <DeleteRepo repository={repository} store={store} />, + options.get() ); expect(navLink.text()).toBeNull(); }); @@ -80,6 +81,8 @@ describe("DeleteRepo", () => { } }; + store.dispatch = jest.fn(); + const navLink = mount( <DeleteRepo repository={repository} @@ -90,6 +93,6 @@ describe("DeleteRepo", () => { ); navLink.find("button").simulate("click"); - expect(calledUrl).toBe("/repos"); + expect(store.dispatch.mock.calls.length).toBe(1); }); }); diff --git a/scm-ui/src/users/containers/DeleteUser.test.js b/scm-ui/src/users/containers/DeleteUser.test.js index 312efe51d9..57995c7394 100644 --- a/scm-ui/src/users/containers/DeleteUser.test.js +++ b/scm-ui/src/users/containers/DeleteUser.test.js @@ -1,30 +1,41 @@ import React from "react"; -import { mount, shallow } from "enzyme"; +import { mount } from "enzyme"; import ReactRouterEnzymeContext from "react-router-enzyme-context"; +import configureStore from "redux-mock-store"; import "../../tests/enzyme"; import "../../tests/i18n"; import DeleteUser from "./DeleteUser"; import { confirmAlert } from "@scm-manager/ui-components"; + jest.mock("@scm-manager/ui-components", () => ({ confirmAlert: jest.fn(), Subtitle: require.requireActual("@scm-manager/ui-components").Subtitle, - DeleteButton: require.requireActual("@scm-manager/ui-components").DeleteButton + DeleteButton: require.requireActual("@scm-manager/ui-components").DeleteButton, + ErrorNotification: ({err}) => err ? err.message : null })); const options = new ReactRouterEnzymeContext(); describe("DeleteUser", () => { + + let store; + + beforeEach(() => { + store = configureStore()({}); + }); + it("should render nothing, if the delete link is missing", () => { const user = { _links: {} }; - const navLink = shallow( - <DeleteUser user={user} deleteUser={() => {}} /> + const navLink = mount( + <DeleteUser user={user} store={store} />, + options.get() ); - expect(navLink.text()).toBe(""); + expect(navLink.text()).toBeNull(); }); it("should render the navLink", () => { @@ -37,7 +48,7 @@ describe("DeleteUser", () => { }; const navLink = mount( - <DeleteUser user={user} deleteUser={() => {}} />, + <DeleteUser user={user} store={store} />, options.get() ); expect(navLink.text()).not.toBe(""); @@ -53,7 +64,7 @@ describe("DeleteUser", () => { }; const navLink = mount( - <DeleteUser user={user} deleteUser={() => {}} />, + <DeleteUser user={user} store={store} />, options.get() ); navLink.find("button").simulate("click"); @@ -70,21 +81,18 @@ describe("DeleteUser", () => { } }; - let calledUrl = null; - function capture(user) { - calledUrl = user._links.delete.href; - } + store.dispatch = jest.fn(); const navLink = mount( <DeleteUser user={user} confirmDialog={false} - deleteUser={capture} + store={store} />, options.get() ); navLink.find("button").simulate("click"); - expect(calledUrl).toBe("/users"); + expect(store.dispatch.mock.calls.length).toBe(1); }); }); From 4e72b3686c704f5c7b4e1c1a9e57860978781390 Mon Sep 17 00:00:00 2001 From: Philipp Czora <philipp.czora@cloudogu.com> Date: Wed, 6 Feb 2019 14:23:25 +0100 Subject: [PATCH 101/112] Added NotAllowedExceptionMapper --- .../scm/api/rest/NotAllowedExceptionMapper.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/rest/NotAllowedExceptionMapper.java diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/NotAllowedExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/rest/NotAllowedExceptionMapper.java new file mode 100644 index 0000000000..1d268b6855 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/NotAllowedExceptionMapper.java @@ -0,0 +1,12 @@ +package sonia.scm.api.rest; + +import javax.ws.rs.NotAllowedException; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.Provider; + +@Provider +public class NotAllowedExceptionMapper extends StatusExceptionMapper<NotAllowedException> { + public NotAllowedExceptionMapper() { + super(NotAllowedException.class, Response.Status.METHOD_NOT_ALLOWED); + } +} From 6d7069c914325c2fb64475bef03f5bfc72f60a11 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 6 Feb 2019 14:25:03 +0100 Subject: [PATCH 102/112] fixed selector margin --- scm-ui/styles/scm.scss | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scm-ui/styles/scm.scss b/scm-ui/styles/scm.scss index 1feffc401f..83a0e287fc 100644 --- a/scm-ui/styles/scm.scss +++ b/scm-ui/styles/scm.scss @@ -185,8 +185,11 @@ $fa-font-path: "webfonts"; } } -//panels +// panels .panel { + .panel-heading > .field { + margin-bottom: 0; // replace selector margin + } .panel-block { display: block; } From 6b64bc457c10b3613dcf216ed5b9a5ce72fa7336 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 6 Feb 2019 14:29:31 +0100 Subject: [PATCH 103/112] deleted tests --- .../src/groups/containers/DeleteGroup.test.js | 98 ------------------- .../src/repos/containers/DeleteRepo.test.js | 98 ------------------- .../src/users/containers/DeleteUser.test.js | 98 ------------------- 3 files changed, 294 deletions(-) delete mode 100644 scm-ui/src/groups/containers/DeleteGroup.test.js delete mode 100644 scm-ui/src/repos/containers/DeleteRepo.test.js delete mode 100644 scm-ui/src/users/containers/DeleteUser.test.js diff --git a/scm-ui/src/groups/containers/DeleteGroup.test.js b/scm-ui/src/groups/containers/DeleteGroup.test.js deleted file mode 100644 index 4c38623604..0000000000 --- a/scm-ui/src/groups/containers/DeleteGroup.test.js +++ /dev/null @@ -1,98 +0,0 @@ -import React from "react"; -import { mount } from "enzyme"; -import ReactRouterEnzymeContext from "react-router-enzyme-context"; -import configureStore from "redux-mock-store"; - -import "../../tests/enzyme"; -import "../../tests/i18n"; -import DeleteGroup from "./DeleteGroup"; - -import { confirmAlert } from "@scm-manager/ui-components"; - -jest.mock("@scm-manager/ui-components", () => ({ - confirmAlert: jest.fn(), - Subtitle: require.requireActual("@scm-manager/ui-components").Subtitle, - DeleteButton: require.requireActual("@scm-manager/ui-components").DeleteButton, - ErrorNotification: ({err}) => err ? err.message : null -})); - -const options = new ReactRouterEnzymeContext(); - -describe("DeleteGroupNavLink", () => { - - let store; - - beforeEach(() => { - store = configureStore()({}); - }); - - it("should render nothing, if the delete link is missing", () => { - const group = { - _links: {} - }; - - const navLink = mount( - <DeleteGroup group={group} store={store} />, - options.get() - ); - expect(navLink.text()).toBeNull(); - }); - - it("should render the navLink", () => { - const group = { - _links: { - delete: { - href: "/groups" - } - } - }; - - const navLink = mount( - <DeleteGroup group={group} store={store} />, - options.get() - ); - expect(navLink.text()).not.toBe(""); - }); - - it("should open the confirm dialog on navLink click", () => { - const group = { - _links: { - delete: { - href: "/groups" - } - } - }; - - const navLink = mount( - <DeleteGroup group={group} store={store} />, - options.get() - ); - navLink.find("button").simulate("click"); - - expect(confirmAlert.mock.calls.length).toBe(1); - }); - - it("should call the delete group function with delete url", () => { - const group = { - _links: { - delete: { - href: "/groups" - } - } - }; - - store.dispatch = jest.fn(); - - const navLink = mount( - <DeleteGroup - group={group} - confirmDialog={false} - store={store} - />, - options.get() - ); - navLink.find("button").simulate("click"); - - expect(store.dispatch.mock.calls.length).toBe(1); - }); -}); diff --git a/scm-ui/src/repos/containers/DeleteRepo.test.js b/scm-ui/src/repos/containers/DeleteRepo.test.js deleted file mode 100644 index 4f3ebd1049..0000000000 --- a/scm-ui/src/repos/containers/DeleteRepo.test.js +++ /dev/null @@ -1,98 +0,0 @@ -import React from "react"; -import { mount } from "enzyme"; -import ReactRouterEnzymeContext from "react-router-enzyme-context"; -import configureStore from "redux-mock-store"; - -import "../../tests/enzyme"; -import "../../tests/i18n"; -import DeleteRepo from "./DeleteRepo"; - -import { confirmAlert } from "@scm-manager/ui-components"; - -jest.mock("@scm-manager/ui-components", () => ({ - confirmAlert: jest.fn(), - Subtitle: require.requireActual("@scm-manager/ui-components").Subtitle, - DeleteButton: require.requireActual("@scm-manager/ui-components").DeleteButton, - ErrorNotification: ({err}) => err ? err.message : null -})); - -const options = new ReactRouterEnzymeContext(); - -describe("DeleteRepo", () => { - - let store; - - beforeEach(() => { - store = configureStore()({}); - }); - - it("should render nothing, if the delete link is missing", () => { - const repository = { - _links: {} - }; - - const navLink = mount( - <DeleteRepo repository={repository} store={store} />, - options.get() - ); - expect(navLink.text()).toBeNull(); - }); - - it("should render the navLink", () => { - const repository = { - _links: { - delete: { - href: "/repositories" - } - } - }; - - const navLink = mount( - <DeleteRepo repository={repository} store={store} />, - options.get() - ); - expect(navLink.text()).not.toBe(""); - }); - - it("should open the confirm dialog on navLink click", () => { - const repository = { - _links: { - delete: { - href: "/repositorys" - } - } - }; - - const navLink = mount( - <DeleteRepo repository={repository} store={store} />, - options.get() - ); - navLink.find("button").simulate("click"); - - expect(confirmAlert.mock.calls.length).toBe(1); - }); - - it("should call the delete repository function with delete url", () => { - const repository = { - _links: { - delete: { - href: "/repos" - } - } - }; - - store.dispatch = jest.fn(); - - const navLink = mount( - <DeleteRepo - repository={repository} - confirmDialog={false} - store={store} - />, - options.get() - ); - navLink.find("button").simulate("click"); - - expect(store.dispatch.mock.calls.length).toBe(1); - }); -}); diff --git a/scm-ui/src/users/containers/DeleteUser.test.js b/scm-ui/src/users/containers/DeleteUser.test.js deleted file mode 100644 index 57995c7394..0000000000 --- a/scm-ui/src/users/containers/DeleteUser.test.js +++ /dev/null @@ -1,98 +0,0 @@ -import React from "react"; -import { mount } from "enzyme"; -import ReactRouterEnzymeContext from "react-router-enzyme-context"; -import configureStore from "redux-mock-store"; - -import "../../tests/enzyme"; -import "../../tests/i18n"; -import DeleteUser from "./DeleteUser"; - -import { confirmAlert } from "@scm-manager/ui-components"; - -jest.mock("@scm-manager/ui-components", () => ({ - confirmAlert: jest.fn(), - Subtitle: require.requireActual("@scm-manager/ui-components").Subtitle, - DeleteButton: require.requireActual("@scm-manager/ui-components").DeleteButton, - ErrorNotification: ({err}) => err ? err.message : null -})); - -const options = new ReactRouterEnzymeContext(); - -describe("DeleteUser", () => { - - let store; - - beforeEach(() => { - store = configureStore()({}); - }); - - it("should render nothing, if the delete link is missing", () => { - const user = { - _links: {} - }; - - const navLink = mount( - <DeleteUser user={user} store={store} />, - options.get() - ); - expect(navLink.text()).toBeNull(); - }); - - it("should render the navLink", () => { - const user = { - _links: { - delete: { - href: "/users" - } - } - }; - - const navLink = mount( - <DeleteUser user={user} store={store} />, - options.get() - ); - expect(navLink.text()).not.toBe(""); - }); - - it("should open the confirm dialog on navLink click", () => { - const user = { - _links: { - delete: { - href: "/users" - } - } - }; - - const navLink = mount( - <DeleteUser user={user} store={store} />, - options.get() - ); - navLink.find("button").simulate("click"); - - expect(confirmAlert.mock.calls.length).toBe(1); - }); - - it("should call the delete user function with delete url", () => { - const user = { - _links: { - delete: { - href: "/users" - } - } - }; - - store.dispatch = jest.fn(); - - const navLink = mount( - <DeleteUser - user={user} - confirmDialog={false} - store={store} - />, - options.get() - ); - navLink.find("button").simulate("click"); - - expect(store.dispatch.mock.calls.length).toBe(1); - }); -}); From eb66b68c2a147579203539ea082cbe7bfd47ca22 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra <sebastian.sdorra@cloudogu.com> Date: Wed, 6 Feb 2019 14:10:37 +0000 Subject: [PATCH 104/112] Close branch feature/improved-navi From 37b6e986bc47cc347e3a706a6e5db26b74b63da3 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Wed, 6 Feb 2019 14:11:24 +0000 Subject: [PATCH 105/112] Close branch feature/unify_table From 3df6056f00f162e4faa57c3a9d2acef83af7b894 Mon Sep 17 00:00:00 2001 From: Mohamed Karray <mohamed.karray.sr@gmail.com> Date: Thu, 7 Feb 2019 06:01:13 +0100 Subject: [PATCH 106/112] add ui validation --- .../src/forms/AddEntryToTableField.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/forms/AddEntryToTableField.js b/scm-ui-components/packages/ui-components/src/forms/AddEntryToTableField.js index 013014cd98..24b1ced28a 100644 --- a/scm-ui-components/packages/ui-components/src/forms/AddEntryToTableField.js +++ b/scm-ui-components/packages/ui-components/src/forms/AddEntryToTableField.js @@ -10,7 +10,8 @@ type Props = { buttonLabel: string, fieldLabel: string, errorMessage: string, - helpText?: string + helpText?: string, + validateEntry?: string => boolean }; type State = { @@ -25,6 +26,15 @@ class AddEntryToTableField extends React.Component<Props, State> { }; } + isValid = () => { + const {validateEntry} = this.props; + if (!this.state.entryToAdd || this.state.entryToAdd === "" || !validateEntry) { + return true; + } else { + return validateEntry(this.state.entryToAdd); + } + }; + render() { const { disabled, @@ -39,7 +49,7 @@ class AddEntryToTableField extends React.Component<Props, State> { label={fieldLabel} errorMessage={errorMessage} onChange={this.handleAddEntryChange} - validationError={false} + validationError={!this.isValid()} value={this.state.entryToAdd} onReturnPressed={this.appendEntry} disabled={disabled} @@ -48,7 +58,7 @@ class AddEntryToTableField extends React.Component<Props, State> { <AddButton label={buttonLabel} action={this.addButtonClicked} - disabled={disabled || this.state.entryToAdd ===""} + disabled={disabled || this.state.entryToAdd ==="" || !this.isValid()} /> </div> ); From 0c8ed130b9d84cf118833079d531b8d0a337e0c3 Mon Sep 17 00:00:00 2001 From: Mohamed Karray <mohamed.karray.sr@gmail.com> Date: Thu, 7 Feb 2019 08:35:10 +0000 Subject: [PATCH 107/112] Close branch bugfix/method_not_allowed_exception_mapper From a5d9a13bc0d96059288173a518825dda3bab04c2 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Thu, 7 Feb 2019 09:54:53 +0100 Subject: [PATCH 108/112] added subtitle for git config --- scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js | 3 ++- .../scm-git-plugin/src/main/resources/locales/de/plugins.json | 1 + .../scm-git-plugin/src/main/resources/locales/en/plugins.json | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js b/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js index 80817664d5..aadb58eed6 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js +++ b/scm-plugins/scm-git-plugin/src/main/js/RepositoryConfig.js @@ -2,7 +2,7 @@ import React from "react"; -import {apiClient, BranchSelector, ErrorPage, Loading, SubmitButton} from "@scm-manager/ui-components"; +import {apiClient, BranchSelector, ErrorPage, Loading, Subtitle, SubmitButton} from "@scm-manager/ui-components"; import type {Branch, Repository} from "@scm-manager/ui-types"; import {translate} from "react-i18next"; @@ -113,6 +113,7 @@ class RepositoryConfig extends React.Component<Props, State> { if (!(loadingBranches || loadingDefaultBranch)) { return ( <> + <Subtitle subtitle={t("scm-git-plugin.repo-config.title")}/> {this.renderBranchChangedNotification()} <form onSubmit={this.submit}> <BranchSelector diff --git a/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json b/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json index 8902a57f32..cd88897e74 100644 --- a/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json +++ b/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json @@ -27,6 +27,7 @@ }, "repo-config": { "link": "Konfiguration", + "title": "Git Einstellungen", "default-branch": "Standard Branch", "submit": "Speichern", "error": { diff --git a/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json b/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json index 2b579801dd..551573fb72 100644 --- a/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json +++ b/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json @@ -27,6 +27,7 @@ }, "repo-config": { "link": "Configuration", + "title": "Git Settings", "default-branch": "Default branch", "submit": "Submit", "error": { From 8a53730d03272bc4f450ee91f74437526578fc06 Mon Sep 17 00:00:00 2001 From: Mohamed Karray <mohamed.karray.sr@gmail.com> Date: Thu, 7 Feb 2019 11:00:02 +0100 Subject: [PATCH 109/112] enable login button --- scm-ui/src/containers/Login.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scm-ui/src/containers/Login.js b/scm-ui/src/containers/Login.js index e8c5352d58..3ee9b141ef 100644 --- a/scm-ui/src/containers/Login.js +++ b/scm-ui/src/containers/Login.js @@ -143,7 +143,6 @@ class Login extends React.Component<Props, State> { /> <SubmitButton label={t("login.submit")} - disabled={this.isInValid()} fullWidth={true} loading={loading} /> From 19e06cc4396b74f11e2aad74ff6f7094ab55b88b Mon Sep 17 00:00:00 2001 From: Mohamed Karray <mohamed.karray.sr@gmail.com> Date: Thu, 7 Feb 2019 10:02:51 +0000 Subject: [PATCH 110/112] Close branch bugfix/activate_login_button From f0c2cbb915b8d0684aec740fff63cab0ea1a0b04 Mon Sep 17 00:00:00 2001 From: Florian Scholdei <florian.scholdei@cloudogu.com> Date: Thu, 7 Feb 2019 11:09:26 +0100 Subject: [PATCH 111/112] added subtitle to edit permissions page --- scm-ui/public/locales/de/repos.json | 1 + scm-ui/public/locales/en/repos.json | 1 + scm-ui/src/repos/permissions/containers/Permissions.js | 2 ++ 3 files changed, 4 insertions(+) diff --git a/scm-ui/public/locales/de/repos.json b/scm-ui/public/locales/de/repos.json index fcb073d151..f10157b4de 100644 --- a/scm-ui/public/locales/de/repos.json +++ b/scm-ui/public/locales/de/repos.json @@ -80,6 +80,7 @@ } }, "permission": { + "title": "Berechtigungen bearbeiten", "user": "Benutzer", "group": "Gruppe", "error-title": "Fehler", diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 4aabcf6c72..c30eade1a9 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -80,6 +80,7 @@ } }, "permission": { + "title": "Edit Permissions", "user": "User", "group": "Group", "error-title": "Error", diff --git a/scm-ui/src/repos/permissions/containers/Permissions.js b/scm-ui/src/repos/permissions/containers/Permissions.js index 7c20f503c5..38afea441b 100644 --- a/scm-ui/src/repos/permissions/containers/Permissions.js +++ b/scm-ui/src/repos/permissions/containers/Permissions.js @@ -24,6 +24,7 @@ import { import { Loading, ErrorPage, + Subtitle, LabelWithHelpIcon } from "@scm-manager/ui-components"; import type { @@ -143,6 +144,7 @@ class Permissions extends React.Component<Props> { return ( <div> + <Subtitle subtitle={t("permission.title")} /> <table className="has-background-light table is-hoverable is-fullwidth"> <thead> <tr> From 589926348a7c2a5834d6ab591699d84211ffab2b Mon Sep 17 00:00:00 2001 From: Philipp Czora <philipp.czora@cloudogu.com> Date: Thu, 7 Feb 2019 10:25:03 +0000 Subject: [PATCH 112/112] Close branch feature/add_validation