From 9d43cc745f6188357d830f92e1cc682188ac7e1f Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Thu, 28 Mar 2019 10:16:19 +0100 Subject: [PATCH 01/79] renamed branches url --- scm-ui/src/repos/containers/CreateBranch.js | 13 ++++++++++++ scm-ui/src/repos/containers/RepositoryRoot.js | 20 +++++++++++++------ 2 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 scm-ui/src/repos/containers/CreateBranch.js diff --git a/scm-ui/src/repos/containers/CreateBranch.js b/scm-ui/src/repos/containers/CreateBranch.js new file mode 100644 index 0000000000..be20fe0a03 --- /dev/null +++ b/scm-ui/src/repos/containers/CreateBranch.js @@ -0,0 +1,13 @@ +// @flow +import React from "react"; + +class CreateBranch extends React.Component<> { + + render() { + return ( +

Create placeholder

+ ); + } +} + +export default CreateBranch; diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 62c1800100..c5be3bd9d0 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -12,7 +12,6 @@ import {Redirect, Route, Switch} from "react-router-dom"; import type { Repository } from "@scm-manager/ui-types"; import { - CollapsibleErrorPage, Loading, Navigation, SubNavigation, @@ -23,6 +22,7 @@ import { import { translate } from "react-i18next"; import RepositoryDetails from "../components/RepositoryDetails"; import EditRepo from "./EditRepo"; +import CreateBranch from "./CreateBranch"; import Permissions from "../permissions/containers/Permissions"; import type { History } from "history"; @@ -74,7 +74,7 @@ class RepositoryRoot extends React.Component { matches = (route: any) => { const url = this.matchedUrl(); - const regex = new RegExp(`${url}(/branches)?/?[^/]*/changesets?.*`); + const regex = new RegExp(`${url}(/branch)?/?[^/]*/changesets?.*`); return route.location.pathname.match(regex); }; @@ -86,7 +86,7 @@ class RepositoryRoot extends React.Component { title={t("repositoryRoot.errorTitle")} subtitle={t("repositoryRoot.errorSubtitle")} error={error} - /> + />; } if (!repository || loading) { @@ -156,21 +156,29 @@ class RepositoryRoot extends React.Component { render={() => ( )} /> ( )} /> + ( + + )} + /> Date: Thu, 28 Mar 2019 11:51:00 +0100 Subject: [PATCH 02/79] added branches overview and navlink --- scm-ui/public/locales/de/repos.json | 1 + scm-ui/public/locales/en/repos.json | 1 + .../src/repos/containers/BranchesOverview.js | 98 +++++++++++++++++++ scm-ui/src/repos/containers/RepositoryRoot.js | 17 ++++ 4 files changed, 117 insertions(+) create mode 100644 scm-ui/src/repos/containers/BranchesOverview.js diff --git a/scm-ui/public/locales/de/repos.json b/scm-ui/public/locales/de/repos.json index 141d58d1d2..dc444f59ff 100644 --- a/scm-ui/public/locales/de/repos.json +++ b/scm-ui/public/locales/de/repos.json @@ -26,6 +26,7 @@ "menu": { "navigationLabel": "Repository Navigation", "informationNavLink": "Informationen", + "branchesNavLink": "Branches", "historyNavLink": "Commits", "sourcesNavLink": "Sources", "settingsNavLink": "Einstellungen", diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index f10c771d96..d888d73c29 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -26,6 +26,7 @@ "menu": { "navigationLabel": "Repository Navigation", "informationNavLink": "Information", + "branchesNavLink": "Branches", "historyNavLink": "Commits", "sourcesNavLink": "Sources", "settingsNavLink": "Settings", diff --git a/scm-ui/src/repos/containers/BranchesOverview.js b/scm-ui/src/repos/containers/BranchesOverview.js new file mode 100644 index 0000000000..d13449eeb6 --- /dev/null +++ b/scm-ui/src/repos/containers/BranchesOverview.js @@ -0,0 +1,98 @@ +// @flow +import React from "react"; +import {fetchBranches, getBranches, getFetchBranchesFailure, isFetchBranchesPending} from "../modules/branches"; +import {connect} from "react-redux"; +import type {Branch, Repository} from "@scm-manager/ui-types"; +import {compose} from "redux"; +import {translate} from "react-i18next"; +import {withRouter} from "react-router-dom"; +import {ErrorNotification, Loading} from "@scm-manager/ui-components"; + +type Props = { + repository: Repository, + loading: boolean, + error: Error, + branches: Branch[], + + // dispatch props + fetchBranches: Repository => void, + + // Context props + history: any, + match: any, + t: string => string +}; +class BranchesOverview extends React.Component { + componentDidMount() { + const { + fetchBranches, + repository + } = this.props; + + fetchBranches(repository); + } + + render() { + const { + loading, + error, + } = this.props; + + if (error) { + return ; + } + + if (loading) { + return ; + } + + return <>{this.renderBranches()}; + } + + renderBranches() { + const { branches } = this.props; + + let branchesList = null; + if (branches) { + branchesList = ( +
    + {branches.map((branch, index) => { + return
  • {branch.name}
  • ; + })} +
+ ); + } + return branchesList; + } +} + +const mapStateToProps = (state, ownProps) => { + const { repository } = ownProps; + const loading = isFetchBranchesPending(state, repository); + const error = getFetchBranchesFailure(state, repository); + const branches = getBranches(state, repository); + + return { + repository, + loading, + error, + branches + }; +}; + +const mapDispatchToProps = dispatch => { + return { + fetchBranches: (repository: Repository) => { + dispatch(fetchBranches(repository)); + }, + }; +}; + +export default compose( + translate("repos"), + withRouter, + connect( + mapStateToProps, + mapDispatchToProps + ) +)(BranchesOverview); diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index c5be3bd9d0..535d678e12 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -22,6 +22,7 @@ import { import { translate } from "react-i18next"; import RepositoryDetails from "../components/RepositoryDetails"; import EditRepo from "./EditRepo"; +import BranchesOverview from "./BranchesOverview"; import CreateBranch from "./CreateBranch"; import Permissions from "../permissions/containers/Permissions"; @@ -171,6 +172,14 @@ class RepositoryRoot extends React.Component { /> )} /> + ( + + )} + /> ( @@ -199,6 +208,14 @@ class RepositoryRoot extends React.Component { icon="fas fa-info-circle" label={t("repositoryRoot.menu.informationNavLink")} /> + Date: Thu, 28 Mar 2019 13:40:12 +0100 Subject: [PATCH 03/79] changed code-branch icon for more clarity over commit icon --- .../src/repos/changesets/ChangesetButtonGroup.js | 2 +- scm-ui/src/repos/components/list/RepositoryEntry.js | 2 +- scm-ui/src/repos/containers/RepositoryRoot.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetButtonGroup.js b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetButtonGroup.js index de05efb46e..971f5bf44a 100644 --- a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetButtonGroup.js +++ b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetButtonGroup.js @@ -26,7 +26,7 @@ class ChangesetButtonGroup extends React.Component { + + + ); + } +} + +export default translate("repos")(BranchButtonGroup); diff --git a/scm-ui/src/repos/branches/components/BranchDetailTable.js b/scm-ui/src/repos/branches/components/BranchDetailTable.js index 4577bbee72..15a8b6236e 100644 --- a/scm-ui/src/repos/branches/components/BranchDetailTable.js +++ b/scm-ui/src/repos/branches/components/BranchDetailTable.js @@ -2,6 +2,7 @@ import React from "react"; import type { Repository, Branch } from "@scm-manager/ui-types"; import { translate } from "react-i18next"; +import BranchButtonGroup from "./BranchButtonGroup"; type Props = { repository: Repository, @@ -26,6 +27,12 @@ class BranchDetailTable extends React.Component { {repository.name} + + + {t("branch.actions")} + + + ); diff --git a/scm-ui/src/repos/branches/containers/BranchView.js b/scm-ui/src/repos/branches/containers/BranchView.js index a225f0c7fb..459a532b4a 100644 --- a/scm-ui/src/repos/branches/containers/BranchView.js +++ b/scm-ui/src/repos/branches/containers/BranchView.js @@ -3,18 +3,54 @@ import React from "react"; import BranchDetailTable from "../components/BranchDetailTable"; import { ExtensionPoint } from "@scm-manager/ui-extensions"; import type { Repository, Branch } from "@scm-manager/ui-types"; -import {connect} from "react-redux"; -import {translate} from "react-i18next"; -import {getBranch} from "../../modules/branches"; +import { connect } from "react-redux"; +import { translate } from "react-i18next"; +import { withRouter } from "react-router-dom"; +import { + fetchBranchByName, + getFetchBranchFailure, + isFetchBranchPending +} from "../../modules/branches"; +import { ErrorPage, Loading } from "@scm-manager/ui-components"; type Props = { repository: Repository, - branch: Branch // TODO: get branch from props + branchName: string, + loading: boolean, + error: Error, + branch: Branch, + + // dispatch functions + fetchBranchByName: (repository: Repository, branchName: string) => void, + + // context props + t: string => string }; class BranchView extends React.Component { + componentDidMount() { + const { fetchBranchByName, repository, branchName } = this.props; + + fetchBranchByName(repository, branchName); + } + render() { - const { repository, branch } = this.props; + const { loading, error, t, repository, branch } = this.props; + + if (error) { + return ( + + ); + } + + if (!branch || loading) { + return ; + } + return (
@@ -23,7 +59,7 @@ class BranchView extends React.Component {
@@ -33,13 +69,28 @@ class BranchView extends React.Component { const mapStateToProps = (state, ownProps) => { const { repository } = ownProps; - const branch = getBranch(state, repository, "VisualStudio"); // TODO: !!! + const branchName = decodeURIComponent(ownProps.match.params.branch); + const loading = isFetchBranchPending(state, repository, branchName); + const error = getFetchBranchFailure(state, repository, branchName); return { repository, - branch + branchName, + loading, + error }; }; -export default connect( - mapStateToProps -)(translate("repos")(BranchView)); +const mapDispatchToProps = dispatch => { + return { + fetchBranchByName: (repository: string, branchName: string) => { + dispatch(fetchBranchByName(repository, branchName)); + } + }; +}; + +export default withRouter( + connect( + mapStateToProps, + mapDispatchToProps + )(translate("repos")(BranchView)) +); diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index c1b14f4086..8c481f1adc 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -77,7 +77,7 @@ class RepositoryRoot extends React.Component { matchesBranches = (route: any) => { const url = this.matchedUrl(); - const regex = new RegExp(`${url}(/branch)?/?[^/]*/info?.*`); + const regex = new RegExp(`${url}/branch/.+/info`); return route.location.pathname.match(regex); }; diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index f819b07700..7d14a189fa 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -16,6 +16,10 @@ export const FETCH_BRANCHES_FAILURE = `${FETCH_BRANCHES}_${FAILURE_SUFFIX}`; // Fetching branches +export function fetchBranch(repositroy: Repository, branchName: string) { + +} + export function fetchBranches(repository: Repository) { if (!repository._links.branches) { return { From b7178d33323407054983265c9781f188149f3249 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Fri, 29 Mar 2019 14:24:54 +0100 Subject: [PATCH 13/79] sorted branches and show defaultbranch label --- .../repos/branches/components/BranchRow.js | 10 +++-- .../branches/containers/BranchesOverview.js | 41 +++++++++++++++++-- scm-ui/src/repos/modules/branches.js | 4 +- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/scm-ui/src/repos/branches/components/BranchRow.js b/scm-ui/src/repos/branches/components/BranchRow.js index 2a12e64c9e..411eb383a3 100644 --- a/scm-ui/src/repos/branches/components/BranchRow.js +++ b/scm-ui/src/repos/branches/components/BranchRow.js @@ -9,8 +9,12 @@ type Props = { }; export default class BranchRow extends React.Component { - renderLink(to: string, label: string) { - return {label} Default; + renderLink(to: string, label: string, defaultBranch: boolean) { + let showLabel = null; + if(defaultBranch) { + showLabel = Default; + } + return {label} {showLabel}; } render() { @@ -18,7 +22,7 @@ export default class BranchRow extends React.Component { const to = `${baseUrl}/${encodeURIComponent(branch.name)}/info`; return ( - {this.renderLink(to, branch.name)} + {this.renderLink(to, branch.name, branch.defaultBranch)} ); } diff --git a/scm-ui/src/repos/branches/containers/BranchesOverview.js b/scm-ui/src/repos/branches/containers/BranchesOverview.js index 8589d5565f..71e8452fdb 100644 --- a/scm-ui/src/repos/branches/containers/BranchesOverview.js +++ b/scm-ui/src/repos/branches/containers/BranchesOverview.js @@ -35,16 +35,47 @@ type Props = { match: any, t: string => string }; + +// master, default should always be the first one, +// followed by develop the rest should be ordered by its name +export function orderBranches(branches: Branch[]) { + branches.sort((a, b) => { + if (a.defaultBranch && !b.defaultBranch ) { + return -20; + } else if (!a.defaultBranch && b.defaultBranch ) { + return 20; + } else if (a.name === "master" && b.name !== "master") { + return -10; + } else if (a.name !== "master" && b.name === "master") { + return 10; + } else if (a.name === "default" && b.name !== "default") { + return -10; + } else if (a.name !== "default" && b.name === "default") { + return 10; + } else if (a.name === "develop" && b.name !== "develop") { + return -5; + } else if (a.name !== "develop" && b.name === "develop") { + return 5; + } else if (a.name < b.name) { + return -1; + } else if (a.name > b.name) { + return 1; + } + return 0; + }); +} + class BranchesOverview extends React.Component { componentDidMount() { const { fetchBranches, repository } = this.props; - fetchBranches(repository); } render() { const { baseUrl, loading, error, branches, t } = this.props; + orderBranches(branches); + if (error) { return ; } @@ -64,9 +95,13 @@ class BranchesOverview extends React.Component { renderCreateButton() { const { showCreateButton, t } = this.props; - if (showCreateButton || true ) { // TODO + if (showCreateButton || true) { + // TODO return ( - + ); } return null; diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index 7d14a189fa..a0870a588d 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -16,9 +16,7 @@ export const FETCH_BRANCHES_FAILURE = `${FETCH_BRANCHES}_${FAILURE_SUFFIX}`; // Fetching branches -export function fetchBranch(repositroy: Repository, branchName: string) { - -} +export function fetchBranch(repositroy: Repository, branchName: string) {} export function fetchBranches(repository: Repository) { if (!repository._links.branches) { From e6912ef77b2ec1c854caf4a9794ee2d12c1bc2a3 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Fri, 29 Mar 2019 14:54:18 +0100 Subject: [PATCH 14/79] fixed preloading bug for sorting null branches --- scm-ui/src/repos/branches/components/BranchRow.js | 8 ++++++-- .../src/repos/branches/containers/BranchesOverview.js | 10 +++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/scm-ui/src/repos/branches/components/BranchRow.js b/scm-ui/src/repos/branches/components/BranchRow.js index 411eb383a3..873bc066c7 100644 --- a/scm-ui/src/repos/branches/components/BranchRow.js +++ b/scm-ui/src/repos/branches/components/BranchRow.js @@ -11,10 +11,14 @@ type Props = { export default class BranchRow extends React.Component { renderLink(to: string, label: string, defaultBranch: boolean) { let showLabel = null; - if(defaultBranch) { + if (defaultBranch) { showLabel = Default; } - return {label} {showLabel}; + return ( + + {label} {showLabel} + + ); } render() { diff --git a/scm-ui/src/repos/branches/containers/BranchesOverview.js b/scm-ui/src/repos/branches/containers/BranchesOverview.js index 71e8452fdb..b39c821494 100644 --- a/scm-ui/src/repos/branches/containers/BranchesOverview.js +++ b/scm-ui/src/repos/branches/containers/BranchesOverview.js @@ -40,9 +40,9 @@ type Props = { // followed by develop the rest should be ordered by its name export function orderBranches(branches: Branch[]) { branches.sort((a, b) => { - if (a.defaultBranch && !b.defaultBranch ) { + if (a.defaultBranch && !b.defaultBranch) { return -20; - } else if (!a.defaultBranch && b.defaultBranch ) { + } else if (!a.defaultBranch && b.defaultBranch) { return 20; } else if (a.name === "master" && b.name !== "master") { return -10; @@ -74,16 +74,16 @@ class BranchesOverview extends React.Component { render() { const { baseUrl, loading, error, branches, t } = this.props; - orderBranches(branches); - if (error) { return ; } - if (loading) { + if (!branches || loading) { return ; } + orderBranches(branches); + return ( <> From 442e4c4889eb875d6d1f8f7fa37681d5388f397e Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Fri, 29 Mar 2019 15:25:32 +0100 Subject: [PATCH 15/79] first attempt on fetching single branch --- scm-ui/src/repos/modules/branches.js | 71 +++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index a0870a588d..6020ec54de 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -14,9 +14,78 @@ export const FETCH_BRANCHES_PENDING = `${FETCH_BRANCHES}_${PENDING_SUFFIX}`; export const FETCH_BRANCHES_SUCCESS = `${FETCH_BRANCHES}_${SUCCESS_SUFFIX}`; export const FETCH_BRANCHES_FAILURE = `${FETCH_BRANCHES}_${FAILURE_SUFFIX}`; +export const FETCH_BRANCH = "scm/repos/FETCH_BRANCH"; +export const FETCH_BRANCH_PENDING = `${FETCH_BRANCH}_${PENDING_SUFFIX}`; +export const FETCH_BRANCH_SUCCESS = `${FETCH_BRANCH}_${SUCCESS_SUFFIX}`; +export const FETCH_BRANCH_FAILURE = `${FETCH_BRANCH}_${FAILURE_SUFFIX}`; + // Fetching branches -export function fetchBranch(repositroy: Repository, branchName: string) {} +export function fetchBranchByName(link: string, name: string) { + const branchUrl = link.endsWith("/") ? link + name : link + "/" + name; + return fetchBranch(branchUrl, name); +} + +export function fetchBranchPending(name: string): Action { + return { + type: FETCH_BRANCH_PENDING, + payload: name, + itemId: name + }; +} + +export function fetchBranchFailure(name: string, error: Error): Action { + return { + type: FETCH_BRANCH_FAILURE, + payload: name, + itemId: name + }; +} + +export function fetchBranch(link: string, name: string) { + return function(dispatch: any) { + dispatch(fetchBranchPending(name)); + return apiClient + .get(link) + .then(response => { + return response.json(); + }) + .then(data => { + dispatch(fetchBranchSuccess(data)); + }) + .catch(error => { + dispatch(fetchBranchFailure(name, error)); + }); + }; +} + +export function getFetchBranchFailure( + state: Object, + repository: string, + branchName: string +) { + return getFailure( + state, + FETCH_BRANCH, + repository + "/branches/" + branchName + ); +} + +export function isFetchBranchPending( + state: Object, + repository: string, + branchName: string +) { + return isPending(state, FETCH_BRANCH, repository + "/branches/" + branchName); +} + +export function fetchBranchSuccess(branch: Branch): Action { + return { + type: FETCH_BRANCH_SUCCESS, + payload: branch, + itemId: branch.name + }; +} export function fetchBranches(repository: Repository) { if (!repository._links.branches) { From bb5e3c9c58b83c6942c0fa3c6bdf2a86ced6a896 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Mon, 1 Apr 2019 09:52:39 +0200 Subject: [PATCH 16/79] added gitbranchinformation binding --- .../src/main/js/CloneInformation.js | 4 +- .../src/main/js/GitBranchInformation.js | 33 +++++++++++++ .../src/main/js/GitConfigurationForm.js | 48 +++++++++---------- .../scm-git-plugin/src/main/js/index.js | 6 +++ .../main/resources/locales/de/plugins.json | 10 ++-- .../main/resources/locales/en/plugins.json | 2 + .../repos/branches/containers/BranchView.js | 4 +- 7 files changed, 74 insertions(+), 33 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/main/js/GitBranchInformation.js diff --git a/scm-plugins/scm-git-plugin/src/main/js/CloneInformation.js b/scm-plugins/scm-git-plugin/src/main/js/CloneInformation.js index 177e662335..cdd08927e4 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/CloneInformation.js +++ b/scm-plugins/scm-git-plugin/src/main/js/CloneInformation.js @@ -8,11 +8,10 @@ type Props = { repository: Repository, // context props - t: (string) => string + t: string => string }; class CloneInformation extends React.Component { - render() { const { url, repository, t } = this.props; @@ -51,7 +50,6 @@ class CloneInformation extends React.Component { ); } - } export default translate("plugins")(CloneInformation); diff --git a/scm-plugins/scm-git-plugin/src/main/js/GitBranchInformation.js b/scm-plugins/scm-git-plugin/src/main/js/GitBranchInformation.js new file mode 100644 index 0000000000..1453d7dd5e --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/js/GitBranchInformation.js @@ -0,0 +1,33 @@ +//@flow +import React from "react"; +import type { Repository } from "@scm-manager/ui-types"; +import { translate } from "react-i18next"; + +type Props = { + repository: Repository, + t: string => string +}; + +class GitBranchInformation extends React.Component { + render() { + const { repository, t } = this.props; + + return ( +
+

{t("scm-git-plugin.information.fetch")}

+
+          git fetch
+        
+

{t("scm-git-plugin.information.checkout")}

+
+          
+            git checkout -placeholder-
+            
+
+
+
+ ); // TODO: Placeholder + } +} + +export default translate("plugins")(GitBranchInformation); diff --git a/scm-plugins/scm-git-plugin/src/main/js/GitConfigurationForm.js b/scm-plugins/scm-git-plugin/src/main/js/GitConfigurationForm.js index 692611510f..bc7e51102a 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/GitConfigurationForm.js +++ b/scm-plugins/scm-git-plugin/src/main/js/GitConfigurationForm.js @@ -10,7 +10,7 @@ type Configuration = { gcExpression?: string, nonFastForwardDisallowed: boolean, _links: Links -} +}; type Props = { initialConfiguration: Configuration, @@ -19,25 +19,24 @@ type Props = { onConfigurationChange: (Configuration, boolean) => void, // context props - t: (string) => string -} + t: string => string +}; -type State = Configuration & { - -} +type State = Configuration & {}; class GitConfigurationForm extends React.Component { - constructor(props: Props) { super(props); this.state = { ...props.initialConfiguration }; } - handleChange = (value: any, name: string) => { - this.setState({ - [name]: value - }, () => this.props.onConfigurationChange(this.state, true)); + this.setState( + { + [name]: value + }, + () => this.props.onConfigurationChange(this.state, true) + ); }; render() { @@ -46,24 +45,25 @@ class GitConfigurationForm extends React.Component { return ( <> - - ); } - } export default translate("plugins")(GitConfigurationForm); 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 43e3950beb..0dafbe4227 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/index.js +++ b/scm-plugins/scm-git-plugin/src/main/js/index.js @@ -6,6 +6,7 @@ import GitAvatar from "./GitAvatar"; import {ConfigurationBinder as cfgBinder} from "@scm-manager/ui-components"; import GitGlobalConfiguration from "./GitGlobalConfiguration"; +import GitBranchInformation from "./GitBranchInformation"; import GitMergeInformation from "./GitMergeInformation"; import RepositoryConfig from "./RepositoryConfig"; @@ -20,6 +21,11 @@ binder.bind( ProtocolInformation, gitPredicate ); +binder.bind( + "repos.branch-details.information", + GitBranchInformation, + gitPredicate +); binder.bind( "repos.repository-merge.information", GitMergeInformation, 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 578d859c8e..e150ceb42b 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,11 @@ { "scm-git-plugin": { "information": { - "clone" : "Repository klonen", - "create" : "Neues Repository erstellen", - "replace" : "Ein bestehendes Repository aktualisieren", + "clone": "Repository klonen", + "create": "Neues Repository erstellen", + "replace": "Ein bestehendes Repository aktualisieren", + "fetch": "Remote-Änderungen herunterladen", + "checkout": "Branch wechseln", "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.", @@ -37,7 +39,7 @@ "success": "Der standard Branch wurde geändert!" } }, - "permissions" : { + "permissions": { "configuration": { "read,write": { "git": { 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 bea0a08dc9..22eb46b9f8 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 @@ -4,6 +4,8 @@ "clone": "Clone the repository", "create": "Create a new repository", "replace": "Push an existing repository", + "fetch": "Get remote changes", + "checkout": "Switch branch", "merge": { "heading": "How to merge source branch into target branch", "checkout": "1. Make sure your workspace is clean and checkout target branch", diff --git a/scm-ui/src/repos/branches/containers/BranchView.js b/scm-ui/src/repos/branches/containers/BranchView.js index 459a532b4a..3fe2985fb5 100644 --- a/scm-ui/src/repos/branches/containers/BranchView.js +++ b/scm-ui/src/repos/branches/containers/BranchView.js @@ -82,8 +82,8 @@ const mapStateToProps = (state, ownProps) => { const mapDispatchToProps = dispatch => { return { - fetchBranchByName: (repository: string, branchName: string) => { - dispatch(fetchBranchByName(repository, branchName)); + fetchBranchByName: (repository: Repository, branchName: string) => { + dispatch(fetchBranchByName(repository._links.branches.href, branchName)); } }; }; From eaf3641d6f6cb64e7775c354808c638661aa2a90 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Mon, 1 Apr 2019 13:12:34 +0200 Subject: [PATCH 17/79] fixed get branch, added tests --- .../repos/branches/containers/BranchView.js | 8 +++- scm-ui/src/repos/modules/branches.js | 45 ++++++++++--------- scm-ui/src/repos/modules/branches.test.js | 43 ++++++++++++++++++ 3 files changed, 72 insertions(+), 24 deletions(-) diff --git a/scm-ui/src/repos/branches/containers/BranchView.js b/scm-ui/src/repos/branches/containers/BranchView.js index 3fe2985fb5..fd65f6eab0 100644 --- a/scm-ui/src/repos/branches/containers/BranchView.js +++ b/scm-ui/src/repos/branches/containers/BranchView.js @@ -8,6 +8,7 @@ import { translate } from "react-i18next"; import { withRouter } from "react-router-dom"; import { fetchBranchByName, + getBranchByName, getFetchBranchFailure, isFetchBranchPending } from "../../modules/branches"; @@ -70,11 +71,14 @@ class BranchView extends React.Component { const mapStateToProps = (state, ownProps) => { const { repository } = ownProps; const branchName = decodeURIComponent(ownProps.match.params.branch); - const loading = isFetchBranchPending(state, repository, branchName); - const error = getFetchBranchFailure(state, repository, branchName); + const branch = getBranchByName(state, branchName); + console.log(branchName + " Branch:",branch); + const loading = isFetchBranchPending(state, branchName); + const error = getFetchBranchFailure(state, branchName); return { repository, branchName, + branch, loading, error }; diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index 6020ec54de..15dfb9c714 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -34,6 +34,14 @@ export function fetchBranchPending(name: string): Action { }; } +export function fetchBranchSuccess(branch: Branch): Action { + return { + type: FETCH_BRANCH_SUCCESS, + payload: branch, + itemId: branch.name + }; +} + export function fetchBranchFailure(name: string, error: Error): Action { return { type: FETCH_BRANCH_FAILURE, @@ -59,32 +67,19 @@ export function fetchBranch(link: string, name: string) { }; } -export function getFetchBranchFailure( - state: Object, - repository: string, - branchName: string -) { - return getFailure( - state, - FETCH_BRANCH, - repository + "/branches/" + branchName - ); +export function getBranchByName(state: Object, name: string) { + console.log("State:", state); + if (state.branches) { + return state.branches[name]; + } } -export function isFetchBranchPending( - state: Object, - repository: string, - branchName: string -) { - return isPending(state, FETCH_BRANCH, repository + "/branches/" + branchName); +export function isFetchBranchPending(state: Object, name: string) { + return isPending(state, FETCH_BRANCH, name); } -export function fetchBranchSuccess(branch: Branch): Action { - return { - type: FETCH_BRANCH_SUCCESS, - payload: branch, - itemId: branch.name - }; +export function getFetchBranchFailure(state: Object, name: string) { + return getFailure(state, FETCH_BRANCH, name); } export function fetchBranches(repository: Repository) { @@ -154,6 +149,12 @@ export default function reducer( ...state, [key]: extractBranchesFromPayload(payload.data) }; + case FETCH_BRANCH_SUCCESS: + return { + ...state, + [action.payload.name]: action.payload + }; + default: return state; } diff --git a/scm-ui/src/repos/modules/branches.test.js b/scm-ui/src/repos/modules/branches.test.js index bcbae28611..1f33992b65 100644 --- a/scm-ui/src/repos/modules/branches.test.js +++ b/scm-ui/src/repos/modules/branches.test.js @@ -6,7 +6,14 @@ import reducer, { FETCH_BRANCHES_FAILURE, FETCH_BRANCHES_PENDING, FETCH_BRANCHES_SUCCESS, + FETCH_BRANCH, + FETCH_BRANCH_PENDING, + FETCH_BRANCH_SUCCESS, + FETCH_BRANCH_FAILURE, fetchBranches, + fetchBranchByName, + fetchBranchSuccess, + fetchBranch, getBranch, getBranches, getFetchBranchesFailure, @@ -88,6 +95,32 @@ describe("branches", () => { expect(store.getActions()[1].type).toEqual(FETCH_BRANCHES_FAILURE); }); }); + + it("should successfully fetch single branch", () => { + fetchMock.getOnce(URL + "/branch1", branch1); + + const store = mockStore({}); + return store.dispatch(fetchBranchByName(URL, "branch1")).then(() => { + const actions = store.getActions(); + expect(actions[0].type).toEqual(FETCH_BRANCH_PENDING); + expect(actions[1].type).toEqual(FETCH_BRANCH_SUCCESS); + expect(actions[1].payload).toBeDefined(); + }); + }); + + it("should fail fetching single branch on HTTP 500", () => { + fetchMock.getOnce(URL + "/branch2", { + status: 500 + }); + + const store = mockStore({}); + return store.dispatch(fetchBranchByName(URL, "branch2")).then(() => { + const actions = store.getActions(); + expect(actions[0].type).toEqual(FETCH_BRANCH_PENDING); + expect(actions[1].type).toEqual(FETCH_BRANCH_FAILURE); + expect(actions[1].payload).toBeDefined(); + }); + }); }); describe("branches reducer", () => { @@ -123,6 +156,16 @@ describe("branches", () => { expect(newState["hitchhiker/heartOfGold"]).toContain(branch3); }); + + it("should update state according to FETCH_BRANCH_SUCCESS action", () => { + const newState = reducer({}, fetchBranchSuccess(branch3)); + expect(newState["branch3"]).toBe(branch3); + }); + + it("should update state according to FETCH_BRANCH_SUCCESS action", () => { + const newState = reducer({}, fetchBranch(URL + "/branch1", "branch1")); + expect(newState["branch1"]).toBe(branch1); + }); }); describe("branch selectors", () => { From 5420884f5a2e6a39ad1543afd357464cc2b56094 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Mon, 1 Apr 2019 13:47:41 +0200 Subject: [PATCH 18/79] added hg and git branch manual --- .../src/main/js/GitBranchInformation.js | 13 ++++---- .../src/main/js/HgBranchInformation.js | 30 +++++++++++++++++++ .../scm-hg-plugin/src/main/js/index.js | 19 ++++++++++-- .../main/resources/locales/de/plugins.json | 4 ++- .../main/resources/locales/en/plugins.json | 4 ++- .../branches/components/BranchDetailTable.js | 21 ++++++++++--- 6 files changed, 75 insertions(+), 16 deletions(-) create mode 100644 scm-plugins/scm-hg-plugin/src/main/js/HgBranchInformation.js diff --git a/scm-plugins/scm-git-plugin/src/main/js/GitBranchInformation.js b/scm-plugins/scm-git-plugin/src/main/js/GitBranchInformation.js index 1453d7dd5e..b4c1873e9f 100644 --- a/scm-plugins/scm-git-plugin/src/main/js/GitBranchInformation.js +++ b/scm-plugins/scm-git-plugin/src/main/js/GitBranchInformation.js @@ -1,16 +1,16 @@ //@flow import React from "react"; -import type { Repository } from "@scm-manager/ui-types"; +import type { Branch } from "@scm-manager/ui-types"; import { translate } from "react-i18next"; type Props = { - repository: Repository, + branch: Branch, t: string => string }; class GitBranchInformation extends React.Component { render() { - const { repository, t } = this.props; + const { branch, t } = this.props; return (
@@ -20,13 +20,10 @@ class GitBranchInformation extends React.Component {

{t("scm-git-plugin.information.checkout")}

-          
-            git checkout -placeholder-
-            
-
+ git checkout {branch.name}
- ); // TODO: Placeholder + ); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/js/HgBranchInformation.js b/scm-plugins/scm-hg-plugin/src/main/js/HgBranchInformation.js new file mode 100644 index 0000000000..358a682054 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/js/HgBranchInformation.js @@ -0,0 +1,30 @@ +//@flow +import React from "react"; +import type { Branch } from "@scm-manager/ui-types"; +import { translate } from "react-i18next"; + +type Props = { + branch: Branch, + t: string => string +}; + +class HgBranchInformation extends React.Component { + render() { + const { branch, t } = this.props; + + return ( +
+

{t("scm-hg-plugin.information.fetch")}

+
+          hg pull
+        
+

{t("scm-hg-plugin.information.checkout")}

+
+          hg update {branch.name}
+        
+
+ ); + } +} + +export default translate("plugins")(HgBranchInformation); diff --git a/scm-plugins/scm-hg-plugin/src/main/js/index.js b/scm-plugins/scm-hg-plugin/src/main/js/index.js index a1fa72f5bd..9df4512d10 100644 --- a/scm-plugins/scm-hg-plugin/src/main/js/index.js +++ b/scm-plugins/scm-hg-plugin/src/main/js/index.js @@ -4,14 +4,29 @@ import ProtocolInformation from "./ProtocolInformation"; import HgAvatar from "./HgAvatar"; import { ConfigurationBinder as cfgBinder } from "@scm-manager/ui-components"; import HgGlobalConfiguration from "./HgGlobalConfiguration"; +import HgBranchInformation from "./HgBranchInformation"; const hgPredicate = (props: Object) => { return props.repository && props.repository.type === "hg"; }; -binder.bind("repos.repository-details.information", ProtocolInformation, hgPredicate); +binder.bind( + "repos.repository-details.information", + ProtocolInformation, + hgPredicate +); +binder.bind( + "repos.branch-details.information", + HgBranchInformation, + hgPredicate +); binder.bind("repos.repository-avatar", HgAvatar, hgPredicate); // bind global configuration -cfgBinder.bindGlobal("/hg", "scm-hg-plugin.config.link", "hgConfig", HgGlobalConfiguration); +cfgBinder.bindGlobal( + "/hg", + "scm-hg-plugin.config.link", + "hgConfig", + HgGlobalConfiguration +); 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 63a8cc8a98..d32847a3af 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 @@ -3,7 +3,9 @@ "information": { "clone" : "Repository klonen", "create" : "Neues Repository erstellen", - "replace" : "Ein bestehendes Repository aktualisieren" + "replace" : "Ein bestehendes Repository aktualisieren", + "fetch": "Remote-Änderungen herunterladen", + "checkout": "Branch wechseln" }, "config": { "link": "Mercurial", 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 a5d05d5796..3792bd4a47 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 @@ -3,7 +3,9 @@ "information": { "clone" : "Clone the repository", "create" : "Create a new repository", - "replace" : "Push an existing repository" + "replace" : "Push an existing repository", + "fetch": "Get remote changes", + "checkout": "Switch branch" }, "config": { "link": "Mercurial", diff --git a/scm-ui/src/repos/branches/components/BranchDetailTable.js b/scm-ui/src/repos/branches/components/BranchDetailTable.js index 15a8b6236e..08935aa26c 100644 --- a/scm-ui/src/repos/branches/components/BranchDetailTable.js +++ b/scm-ui/src/repos/branches/components/BranchDetailTable.js @@ -14,12 +14,15 @@ type Props = { class BranchDetailTable extends React.Component { render() { const { repository, branch, t } = this.props; + return ( - + - + -
{t("branch.name")}branch.name + {branch.name} {this.renderDefaultBranch()} +
@@ -28,15 +31,25 @@ class BranchDetailTable extends React.Component { {repository.name}
- {t("branch.actions")} + {t("branch.actions")} +
); } + + renderDefaultBranch() { + const { branch } = this.props; + + let defaultLabel = null; + if (branch.defaultBranch) { + defaultLabel = Default; + } + return defaultLabel; + } } export default translate("repos")(BranchDetailTable); From 7f1aeb0492879c823745b089374f97e0576dfe96 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Mon, 1 Apr 2019 15:13:44 +0200 Subject: [PATCH 19/79] restyled branches overview tabled, fixed en/decoded branch name --- scm-ui/src/repos/branches/components/BranchDetailTable.js | 8 ++++---- scm-ui/src/repos/branches/containers/BranchView.js | 1 - scm-ui/src/repos/modules/branches.js | 3 +-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/scm-ui/src/repos/branches/components/BranchDetailTable.js b/scm-ui/src/repos/branches/components/BranchDetailTable.js index 08935aa26c..53718c5b6d 100644 --- a/scm-ui/src/repos/branches/components/BranchDetailTable.js +++ b/scm-ui/src/repos/branches/components/BranchDetailTable.js @@ -19,19 +19,19 @@ class BranchDetailTable extends React.Component { - + - - + diff --git a/scm-ui/src/repos/branches/containers/BranchView.js b/scm-ui/src/repos/branches/containers/BranchView.js index fd65f6eab0..4774e2825a 100644 --- a/scm-ui/src/repos/branches/containers/BranchView.js +++ b/scm-ui/src/repos/branches/containers/BranchView.js @@ -72,7 +72,6 @@ const mapStateToProps = (state, ownProps) => { const { repository } = ownProps; const branchName = decodeURIComponent(ownProps.match.params.branch); const branch = getBranchByName(state, branchName); - console.log(branchName + " Branch:",branch); const loading = isFetchBranchPending(state, branchName); const error = getFetchBranchFailure(state, branchName); return { diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index 15dfb9c714..5c9a63b5d6 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -22,7 +22,7 @@ export const FETCH_BRANCH_FAILURE = `${FETCH_BRANCH}_${FAILURE_SUFFIX}`; // Fetching branches export function fetchBranchByName(link: string, name: string) { - const branchUrl = link.endsWith("/") ? link + name : link + "/" + name; + const branchUrl = link.endsWith("/") ? link + encodeURIComponent(name) : link + "/" + encodeURIComponent(name); return fetchBranch(branchUrl, name); } @@ -68,7 +68,6 @@ export function fetchBranch(link: string, name: string) { } export function getBranchByName(state: Object, name: string) { - console.log("State:", state); if (state.branches) { return state.branches[name]; } From d6ead4fa8234f69c4ce631c636f031ec158b71cd Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Mon, 1 Apr 2019 15:25:55 +0200 Subject: [PATCH 20/79] created repo specific xhangeset and sourceslink --- .../branches/components/BranchButtonGroup.js | 5 ++-- scm-ui/src/repos/modules/branches.js | 23 +++++++++++++++++-- scm-ui/src/repos/modules/branches.test.js | 5 ---- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/scm-ui/src/repos/branches/components/BranchButtonGroup.js b/scm-ui/src/repos/branches/components/BranchButtonGroup.js index 2ed49d7b1f..b7c7a843ec 100644 --- a/scm-ui/src/repos/branches/components/BranchButtonGroup.js +++ b/scm-ui/src/repos/branches/components/BranchButtonGroup.js @@ -3,6 +3,7 @@ import React from "react"; import type { Repository, Branch } from "@scm-manager/ui-types"; import { ButtonGroup, Button } from "@scm-manager/ui-components"; import { translate } from "react-i18next"; +import { createChangesetLink, createSourcesLink } from "../../modules/branches"; type Props = { repository: Repository, @@ -16,8 +17,8 @@ class BranchButtonGroup extends React.Component { render() { const { repository, branch, t } = this.props; - const changesetLink = ""; - const sourcesLink = ""; + const changesetLink = createChangesetLink(repository, branch); + const sourcesLink = createSourcesLink(repository, branch); return ( diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index 5c9a63b5d6..8397ac3011 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -5,7 +5,12 @@ import { SUCCESS_SUFFIX } from "../../modules/types"; import { apiClient } from "@scm-manager/ui-components"; -import type { Action, Branch, Repository } from "@scm-manager/ui-types"; +import type { + Action, + Branch, + Changeset, + Repository +} from "@scm-manager/ui-types"; import { isPending } from "../../modules/pending"; import { getFailure } from "../../modules/failure"; @@ -22,7 +27,9 @@ export const FETCH_BRANCH_FAILURE = `${FETCH_BRANCH}_${FAILURE_SUFFIX}`; // Fetching branches export function fetchBranchByName(link: string, name: string) { - const branchUrl = link.endsWith("/") ? link + encodeURIComponent(name) : link + "/" + encodeURIComponent(name); + const branchUrl = link.endsWith("/") + ? link + encodeURIComponent(name) + : link + "/" + encodeURIComponent(name); return fetchBranch(branchUrl, name); } @@ -203,3 +210,15 @@ function createKey(repository: Repository): string { const { namespace, name } = repository; return `${namespace}/${name}`; } + +export function createChangesetLink(repository: Repository, branch: Branch) { + return `/repo/${repository.namespace}/${ + repository.name + }/branch/${encodeURIComponent(branch.name)}/changesets/`; +} + +export function createSourcesLink(repository: Repository, branch: Branch) { + return `/repo/${repository.namespace}/${ + repository.name + }/sources/${encodeURIComponent(branch.name)}/`; +} diff --git a/scm-ui/src/repos/modules/branches.test.js b/scm-ui/src/repos/modules/branches.test.js index 1f33992b65..5e9b4e0f1f 100644 --- a/scm-ui/src/repos/modules/branches.test.js +++ b/scm-ui/src/repos/modules/branches.test.js @@ -161,11 +161,6 @@ describe("branches", () => { const newState = reducer({}, fetchBranchSuccess(branch3)); expect(newState["branch3"]).toBe(branch3); }); - - it("should update state according to FETCH_BRANCH_SUCCESS action", () => { - const newState = reducer({}, fetchBranch(URL + "/branch1", "branch1")); - expect(newState["branch1"]).toBe(branch1); - }); }); describe("branch selectors", () => { From 2ba57dc141d29fc564890488e536b35ce20bda3a Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Mon, 1 Apr 2019 15:32:29 +0200 Subject: [PATCH 21/79] small code improvement --- scm-ui/src/repos/modules/branches.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index 8397ac3011..62299801d6 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -27,10 +27,11 @@ export const FETCH_BRANCH_FAILURE = `${FETCH_BRANCH}_${FAILURE_SUFFIX}`; // Fetching branches export function fetchBranchByName(link: string, name: string) { - const branchUrl = link.endsWith("/") - ? link + encodeURIComponent(name) - : link + "/" + encodeURIComponent(name); - return fetchBranch(branchUrl, name); + let endsWith = ""; + if(!link.endsWith("/")) { + endsWith = "/"; + } + return fetchBranch(link + endsWith + encodeURIComponent(name), name); } export function fetchBranchPending(name: string): Action { From d8a33e0fc219117e7ad39ddf5b21cd83dcfbc19c Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Mon, 1 Apr 2019 15:58:07 +0200 Subject: [PATCH 22/79] vcentered default branch label + more spacing --- .../branches/components/BranchDetailTable.js | 24 +++++++++++++------ .../repos/branches/components/BranchRow.js | 20 +++++++++++++--- scm-ui/src/repos/modules/branches.js | 1 - 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/scm-ui/src/repos/branches/components/BranchDetailTable.js b/scm-ui/src/repos/branches/components/BranchDetailTable.js index 53718c5b6d..27c4907a53 100644 --- a/scm-ui/src/repos/branches/components/BranchDetailTable.js +++ b/scm-ui/src/repos/branches/components/BranchDetailTable.js @@ -2,13 +2,23 @@ import React from "react"; import type { Repository, Branch } from "@scm-manager/ui-types"; import { translate } from "react-i18next"; +import injectSheet from "react-jss"; +import classNames from "classnames"; import BranchButtonGroup from "./BranchButtonGroup"; type Props = { repository: Repository, branch: Branch, // context props - t: string => string + t: string => string, + classes: any +}; + +const styles = { + tag: { + marginLeft: "0.75rem", + verticalAlign: "inherit" + } }; class BranchDetailTable extends React.Component { @@ -25,9 +35,7 @@ class BranchDetailTable extends React.Component { - + @@ -42,14 +50,16 @@ class BranchDetailTable extends React.Component { } renderDefaultBranch() { - const { branch } = this.props; + const { branch, classes } = this.props; let defaultLabel = null; if (branch.defaultBranch) { - defaultLabel = Default; + defaultLabel = ( + Default + ); } return defaultLabel; } } -export default translate("repos")(BranchDetailTable); +export default injectSheet(styles)(translate("repos")(BranchDetailTable)); diff --git a/scm-ui/src/repos/branches/components/BranchRow.js b/scm-ui/src/repos/branches/components/BranchRow.js index 873bc066c7..06cb52c268 100644 --- a/scm-ui/src/repos/branches/components/BranchRow.js +++ b/scm-ui/src/repos/branches/components/BranchRow.js @@ -2,17 +2,29 @@ import React from "react"; import { Link } from "react-router-dom"; import type { Branch } from "@scm-manager/ui-types"; +import injectSheet from "react-jss"; +import classNames from "classnames"; type Props = { baseUrl: string, - branch: Branch + branch: Branch, + classes: any }; -export default class BranchRow extends React.Component { +const styles = { + tag: { + marginLeft: "0.75rem", + verticalAlign: "inherit" + } +}; + +class BranchRow extends React.Component { renderLink(to: string, label: string, defaultBranch: boolean) { + const { classes } = this.props; + let showLabel = null; if (defaultBranch) { - showLabel = Default; + showLabel = Default; } return ( @@ -31,3 +43,5 @@ export default class BranchRow extends React.Component { ); } } + +export default injectSheet(styles)(BranchRow); diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index 62299801d6..9468443fea 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -8,7 +8,6 @@ import { apiClient } from "@scm-manager/ui-components"; import type { Action, Branch, - Changeset, Repository } from "@scm-manager/ui-types"; import { isPending } from "../../modules/pending"; From db0a835bca27ea52f03e6cb81d7ae29910b21b03 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Tue, 2 Apr 2019 09:27:34 +0200 Subject: [PATCH 23/79] added create branch trans, fixed branchtable trans, moved branches modules --- scm-ui/public/locales/de/repos.json | 5 ++++- scm-ui/public/locales/en/repos.json | 5 ++++- scm-ui/src/createReduxStore.js | 2 +- .../branches/components/BranchButtonGroup.js | 2 +- .../src/repos/branches/components/BranchForm.js | 16 ++++++++++++++++ .../src/repos/branches/components/BranchTable.js | 2 +- .../src/repos/branches/containers/BranchView.js | 2 +- .../branches/containers/BranchesOverview.js | 2 +- .../src/repos/{ => branches}/modules/branches.js | 6 +++--- .../{ => branches}/modules/branches.test.js | 2 +- scm-ui/src/repos/containers/ChangesetsRoot.js | 2 +- scm-ui/src/repos/sources/containers/Sources.js | 2 +- 12 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 scm-ui/src/repos/branches/components/BranchForm.js rename scm-ui/src/repos/{ => branches}/modules/branches.js (97%) rename scm-ui/src/repos/{ => branches}/modules/branches.test.js (99%) diff --git a/scm-ui/public/locales/de/repos.json b/scm-ui/public/locales/de/repos.json index 8350ecc581..3a90f9080a 100644 --- a/scm-ui/public/locales/de/repos.json +++ b/scm-ui/public/locales/de/repos.json @@ -54,7 +54,10 @@ "branches": "Branches" }, "create": { - "title": "Branch erstellen" + "title": "Branch erstellen", + "source": "Quellbranch", + "name": "Name", + "submit": "Branch erstellen" } }, "branch": { diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index fef882c90e..6adede7b82 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -54,7 +54,10 @@ "branches": "Branches" }, "create": { - "title": "Create Branch" + "title": "Create Branch", + "source": "Source Branch", + "name": "Name", + "submit": "Create Branch" } }, "branch": { diff --git a/scm-ui/src/createReduxStore.js b/scm-ui/src/createReduxStore.js index cd24f46de2..eb0586d657 100644 --- a/scm-ui/src/createReduxStore.js +++ b/scm-ui/src/createReduxStore.js @@ -19,7 +19,7 @@ import namespaceStrategies from "./config/modules/namespaceStrategies"; import indexResources from "./modules/indexResource"; import type { BrowserHistory } from "history/createBrowserHistory"; -import branches from "./repos/modules/branches"; +import branches from "./repos/branches/modules/branches"; function createReduxStore(history: BrowserHistory) { const composeEnhancers = diff --git a/scm-ui/src/repos/branches/components/BranchButtonGroup.js b/scm-ui/src/repos/branches/components/BranchButtonGroup.js index b7c7a843ec..dc170963e9 100644 --- a/scm-ui/src/repos/branches/components/BranchButtonGroup.js +++ b/scm-ui/src/repos/branches/components/BranchButtonGroup.js @@ -3,7 +3,7 @@ import React from "react"; import type { Repository, Branch } from "@scm-manager/ui-types"; import { ButtonGroup, Button } from "@scm-manager/ui-components"; import { translate } from "react-i18next"; -import { createChangesetLink, createSourcesLink } from "../../modules/branches"; +import { createChangesetLink, createSourcesLink } from "../modules/branches"; type Props = { repository: Repository, diff --git a/scm-ui/src/repos/branches/components/BranchForm.js b/scm-ui/src/repos/branches/components/BranchForm.js new file mode 100644 index 0000000000..614a719338 --- /dev/null +++ b/scm-ui/src/repos/branches/components/BranchForm.js @@ -0,0 +1,16 @@ +//@flow +import React from "react"; + +type Props = {}; + +class CreateBranch extends React.Component { + render() { + return ( + <> +

Form placeholder

+ + ); + } +} + +export default translate("repos")(BranchForm); diff --git a/scm-ui/src/repos/branches/components/BranchTable.js b/scm-ui/src/repos/branches/components/BranchTable.js index e85fa0d625..85dd854f21 100644 --- a/scm-ui/src/repos/branches/components/BranchTable.js +++ b/scm-ui/src/repos/branches/components/BranchTable.js @@ -37,4 +37,4 @@ class BranchTable extends React.Component { } } -export default translate("users")(BranchTable); +export default translate("repos")(BranchTable); diff --git a/scm-ui/src/repos/branches/containers/BranchView.js b/scm-ui/src/repos/branches/containers/BranchView.js index 4774e2825a..5a90812ac9 100644 --- a/scm-ui/src/repos/branches/containers/BranchView.js +++ b/scm-ui/src/repos/branches/containers/BranchView.js @@ -11,7 +11,7 @@ import { getBranchByName, getFetchBranchFailure, isFetchBranchPending -} from "../../modules/branches"; +} from "../modules/branches"; import { ErrorPage, Loading } from "@scm-manager/ui-components"; type Props = { diff --git a/scm-ui/src/repos/branches/containers/BranchesOverview.js b/scm-ui/src/repos/branches/containers/BranchesOverview.js index b39c821494..4f4ca4a75d 100644 --- a/scm-ui/src/repos/branches/containers/BranchesOverview.js +++ b/scm-ui/src/repos/branches/containers/BranchesOverview.js @@ -5,7 +5,7 @@ import { getBranches, getFetchBranchesFailure, isFetchBranchesPending -} from "../../modules/branches"; +} from "../modules/branches"; import { connect } from "react-redux"; import type { Branch, Repository } from "@scm-manager/ui-types"; import { compose } from "redux"; diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/branches/modules/branches.js similarity index 97% rename from scm-ui/src/repos/modules/branches.js rename to scm-ui/src/repos/branches/modules/branches.js index 9468443fea..03c0c1a9eb 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/branches/modules/branches.js @@ -3,15 +3,15 @@ import { FAILURE_SUFFIX, PENDING_SUFFIX, SUCCESS_SUFFIX -} from "../../modules/types"; +} from "../../../modules/types"; import { apiClient } from "@scm-manager/ui-components"; import type { Action, Branch, Repository } from "@scm-manager/ui-types"; -import { isPending } from "../../modules/pending"; -import { getFailure } from "../../modules/failure"; +import { isPending } from "../../../modules/pending"; +import { getFailure } from "../../../modules/failure"; export const FETCH_BRANCHES = "scm/repos/FETCH_BRANCHES"; export const FETCH_BRANCHES_PENDING = `${FETCH_BRANCHES}_${PENDING_SUFFIX}`; diff --git a/scm-ui/src/repos/modules/branches.test.js b/scm-ui/src/repos/branches/modules/branches.test.js similarity index 99% rename from scm-ui/src/repos/modules/branches.test.js rename to scm-ui/src/repos/branches/modules/branches.test.js index 5e9b4e0f1f..754a3803e9 100644 --- a/scm-ui/src/repos/modules/branches.test.js +++ b/scm-ui/src/repos/branches/modules/branches.test.js @@ -1,5 +1,5 @@ import configureMockStore from "redux-mock-store"; -import thunk from "redux-thunk"; +import thunk from "redux-thunk/index"; import fetchMock from "fetch-mock"; import reducer, { FETCH_BRANCHES, diff --git a/scm-ui/src/repos/containers/ChangesetsRoot.js b/scm-ui/src/repos/containers/ChangesetsRoot.js index f6e0c3d359..c4f40e0d74 100644 --- a/scm-ui/src/repos/containers/ChangesetsRoot.js +++ b/scm-ui/src/repos/containers/ChangesetsRoot.js @@ -16,7 +16,7 @@ import { getBranches, getFetchBranchesFailure, isFetchBranchesPending -} from "../modules/branches"; +} from "../branches/modules/branches"; import { compose } from "redux"; type Props = { diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index 05705a61d1..7b87776e73 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -12,7 +12,7 @@ import { getBranches, getFetchBranchesFailure, isFetchBranchesPending -} from "../../modules/branches"; +} from "../../branches/modules/branches"; import { compose } from "redux"; import Content from "./Content"; import { fetchSources, isDirectory } from "../modules/sources"; From e736add3fd6c8bc1b284dc049247d37dd3e1f54e Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Tue, 2 Apr 2019 10:27:00 +0200 Subject: [PATCH 24/79] added branch reducer tests --- scm-ui/src/repos/branches/modules/branches.js | 2 +- .../repos/branches/modules/branches.test.js | 36 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/scm-ui/src/repos/branches/modules/branches.js b/scm-ui/src/repos/branches/modules/branches.js index 03c0c1a9eb..a080a3877f 100644 --- a/scm-ui/src/repos/branches/modules/branches.js +++ b/scm-ui/src/repos/branches/modules/branches.js @@ -41,7 +41,7 @@ export function fetchBranchPending(name: string): Action { }; } -export function fetchBranchSuccess(branch: Branch): Action { +export function fetchBranchSuccess(repo: Repository, branch: Branch): Action { return { type: FETCH_BRANCH_SUCCESS, payload: branch, diff --git a/scm-ui/src/repos/branches/modules/branches.test.js b/scm-ui/src/repos/branches/modules/branches.test.js index 754a3803e9..cc67aaf33a 100644 --- a/scm-ui/src/repos/branches/modules/branches.test.js +++ b/scm-ui/src/repos/branches/modules/branches.test.js @@ -1,5 +1,5 @@ import configureMockStore from "redux-mock-store"; -import thunk from "redux-thunk/index"; +import thunk from "redux-thunk"; import fetchMock from "fetch-mock"; import reducer, { FETCH_BRANCHES, @@ -161,6 +161,40 @@ describe("branches", () => { const newState = reducer({}, fetchBranchSuccess(branch3)); expect(newState["branch3"]).toBe(branch3); }); + + it("should not delete existing branch from state", () => { + const oldState = { + branch1 + }; + + const newState = reducer(oldState, fetchBranchSuccess(branch2)); + expect(newState["branch1"]).toBe(branch1); + expect(newState["branch2"]).toBe(branch2); + }); + + it("should update required branch from state", () => { + const oldState = { + branch1 + }; + + const newBranch1 = { name: "branch1", revision: "revision2" }; + + const newState = reducer(oldState, fetchBranchSuccess(newBranch1)); + expect(newState["branch1"]).not.toBe(branch1); + expect(newState["branch1"]).toBe(newBranch1); + }); + + it("should update required branch from state and keeps old repo", () => { + const oldState = { + repo1: { + branch1 + } + }; + const repo2 = { repo2: { branch3 } }; + const newState = reducer(oldState, fetchBranchSuccess(repo2, branch2)); + expect(newState["repo1"]).toBe({ branch1 }); + expect(newState["repo2"]).toBe({ branch2, branch3 }); + }); }); describe("branch selectors", () => { From 7c61efb20bcc1b67921840da9e543b7e3982ca50 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Tue, 2 Apr 2019 17:31:32 +0200 Subject: [PATCH 25/79] added defaultBranch to Branches type, changed ui-bundler version for better testing experience in intellij, corrected fetchBranch functionality, wrote reducer for single branch and unit tests --- .../packages/ui-types/src/Branches.js | 1 + scm-ui/package.json | 2 +- .../repos/branches/components/BranchRow.js | 12 +-- .../repos/branches/containers/BranchView.js | 16 ++-- scm-ui/src/repos/branches/modules/branches.js | 90 ++++++++++++------- .../repos/branches/modules/branches.test.js | 60 +++++++------ scm-ui/yarn.lock | 7 +- 7 files changed, 112 insertions(+), 76 deletions(-) diff --git a/scm-ui-components/packages/ui-types/src/Branches.js b/scm-ui-components/packages/ui-types/src/Branches.js index f209de34a0..2dee365424 100644 --- a/scm-ui-components/packages/ui-types/src/Branches.js +++ b/scm-ui-components/packages/ui-types/src/Branches.js @@ -4,5 +4,6 @@ import type {Links} from "./hal"; export type Branch = { name: string, revision: string, + defaultBranch?: boolean, _links: Links } diff --git a/scm-ui/package.json b/scm-ui/package.json index 6844ea3ec7..78c7b0e684 100644 --- a/scm-ui/package.json +++ b/scm-ui/package.json @@ -52,7 +52,7 @@ "pre-commit": "jest && flow && eslint src" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.26", + "@scm-manager/ui-bundler": "^0.0.27", "concat": "^1.0.3", "copyfiles": "^2.0.0", "enzyme": "^3.3.0", diff --git a/scm-ui/src/repos/branches/components/BranchRow.js b/scm-ui/src/repos/branches/components/BranchRow.js index 06cb52c268..d8f70575b3 100644 --- a/scm-ui/src/repos/branches/components/BranchRow.js +++ b/scm-ui/src/repos/branches/components/BranchRow.js @@ -19,7 +19,7 @@ const styles = { }; class BranchRow extends React.Component { - renderLink(to: string, label: string, defaultBranch: boolean) { + renderLink(to: string, label: string, defaultBranch?: boolean) { const { classes } = this.props; let showLabel = null; @@ -36,11 +36,11 @@ class BranchRow extends React.Component { render() { const { baseUrl, branch } = this.props; const to = `${baseUrl}/${encodeURIComponent(branch.name)}/info`; - return ( -
- - - ); + return ( + + + + ); } } diff --git a/scm-ui/src/repos/branches/containers/BranchView.js b/scm-ui/src/repos/branches/containers/BranchView.js index 5a90812ac9..959c4519d0 100644 --- a/scm-ui/src/repos/branches/containers/BranchView.js +++ b/scm-ui/src/repos/branches/containers/BranchView.js @@ -7,8 +7,8 @@ import { connect } from "react-redux"; import { translate } from "react-i18next"; import { withRouter } from "react-router-dom"; import { - fetchBranchByName, - getBranchByName, + fetchBranch, + getBranch, getFetchBranchFailure, isFetchBranchPending } from "../modules/branches"; @@ -22,7 +22,7 @@ type Props = { branch: Branch, // dispatch functions - fetchBranchByName: (repository: Repository, branchName: string) => void, + fetchBranch: (repository: Repository, branchName: string) => void, // context props t: string => string @@ -30,9 +30,9 @@ type Props = { class BranchView extends React.Component { componentDidMount() { - const { fetchBranchByName, repository, branchName } = this.props; + const { fetchBranch, repository, branchName } = this.props; - fetchBranchByName(repository, branchName); + fetchBranch(repository, branchName); } render() { @@ -71,7 +71,7 @@ class BranchView extends React.Component { const mapStateToProps = (state, ownProps) => { const { repository } = ownProps; const branchName = decodeURIComponent(ownProps.match.params.branch); - const branch = getBranchByName(state, branchName); + const branch = getBranch(state, repository, branchName); const loading = isFetchBranchPending(state, branchName); const error = getFetchBranchFailure(state, branchName); return { @@ -85,8 +85,8 @@ const mapStateToProps = (state, ownProps) => { const mapDispatchToProps = dispatch => { return { - fetchBranchByName: (repository: Repository, branchName: string) => { - dispatch(fetchBranchByName(repository._links.branches.href, branchName)); + fetchBranch: (repository: Repository, branchName: string) => { + dispatch(fetchBranch(repository, branchName)); } }; }; diff --git a/scm-ui/src/repos/branches/modules/branches.js b/scm-ui/src/repos/branches/modules/branches.js index a080a3877f..dd542d0183 100644 --- a/scm-ui/src/repos/branches/modules/branches.js +++ b/scm-ui/src/repos/branches/modules/branches.js @@ -5,11 +5,7 @@ import { SUCCESS_SUFFIX } from "../../../modules/types"; import { apiClient } from "@scm-manager/ui-components"; -import type { - Action, - Branch, - Repository -} from "@scm-manager/ui-types"; +import type { Action, Branch, Repository } from "@scm-manager/ui-types"; import { isPending } from "../../../modules/pending"; import { getFailure } from "../../../modules/failure"; @@ -25,61 +21,69 @@ export const FETCH_BRANCH_FAILURE = `${FETCH_BRANCH}_${FAILURE_SUFFIX}`; // Fetching branches -export function fetchBranchByName(link: string, name: string) { - let endsWith = ""; - if(!link.endsWith("/")) { - endsWith = "/"; - } - return fetchBranch(link + endsWith + encodeURIComponent(name), name); +function createIdentifier(repository: Repository) { + return repository.namespace + "/" + repository.name; } -export function fetchBranchPending(name: string): Action { +export function fetchBranchPending( + repository: Repository, + name: string +): Action { return { type: FETCH_BRANCH_PENDING, - payload: name, - itemId: name + payload: { repository, name }, + itemId: createIdentifier(repository) + "/" + name }; } -export function fetchBranchSuccess(repo: Repository, branch: Branch): Action { +export function fetchBranchSuccess( + repository: Repository, + branch: Branch +): Action { return { type: FETCH_BRANCH_SUCCESS, - payload: branch, - itemId: branch.name + payload: { repository, branch }, + itemId: createIdentifier(repository) + "/" + branch.name }; } -export function fetchBranchFailure(name: string, error: Error): Action { +export function fetchBranchFailure( + repository: Repository, + name: string, + error: Error +): Action { return { type: FETCH_BRANCH_FAILURE, - payload: name, - itemId: name + payload: { error, repository, name }, + itemId: createIdentifier(repository) + "/" + name }; } -export function fetchBranch(link: string, name: string) { +export function fetchBranch( + repository: Repository, + name: string +) { + let link = repository._links.branches.href; + if (!link.endsWith("/")) { + link += "/"; + } + link += encodeURIComponent(name); return function(dispatch: any) { - dispatch(fetchBranchPending(name)); + dispatch(fetchBranchPending(repository, name)); return apiClient .get(link) .then(response => { return response.json(); }) .then(data => { - dispatch(fetchBranchSuccess(data)); + dispatch(fetchBranchSuccess(repository, data)); }) .catch(error => { - dispatch(fetchBranchFailure(name, error)); + dispatch(fetchBranchFailure(repository, name, error)); }); }; } -export function getBranchByName(state: Object, name: string) { - if (state.branches) { - return state.branches[name]; - } -} - export function isFetchBranchPending(state: Object, name: string) { return isPending(state, FETCH_BRANCH, name); } @@ -138,6 +142,24 @@ export function fetchBranchesFailure(repository: Repository, error: Error) { // Reducers +function reduceBranchSuccess(state, repositoryName, newBranch) { + const newBranches = []; + // we do not use filter, because we try to keep the current order + let found = false; + for (const branch of state[repositoryName] || []) { + if (branch.name === newBranch.name) { + newBranches.push(newBranch); + found = true; + } else { + newBranches.push(branch); + } + } + if (!found) { + newBranches.push(newBranch); + } + return newBranches; +} + type State = { [string]: Branch[] }; export default function reducer( @@ -156,11 +178,15 @@ export default function reducer( [key]: extractBranchesFromPayload(payload.data) }; case FETCH_BRANCH_SUCCESS: + if (!action.payload.repository || !action.payload.branch) { + return state; + } + const newBranch = action.payload.branch; + const repositoryName = createIdentifier(action.payload.repository); return { ...state, - [action.payload.name]: action.payload + [repositoryName]: reduceBranchSuccess(state, repositoryName, newBranch) }; - default: return state; } diff --git a/scm-ui/src/repos/branches/modules/branches.test.js b/scm-ui/src/repos/branches/modules/branches.test.js index cc67aaf33a..30b81975d6 100644 --- a/scm-ui/src/repos/branches/modules/branches.test.js +++ b/scm-ui/src/repos/branches/modules/branches.test.js @@ -11,9 +11,8 @@ import reducer, { FETCH_BRANCH_SUCCESS, FETCH_BRANCH_FAILURE, fetchBranches, - fetchBranchByName, - fetchBranchSuccess, fetchBranch, + fetchBranchSuccess, getBranch, getBranches, getFetchBranchesFailure, @@ -100,7 +99,7 @@ describe("branches", () => { fetchMock.getOnce(URL + "/branch1", branch1); const store = mockStore({}); - return store.dispatch(fetchBranchByName(URL, "branch1")).then(() => { + return store.dispatch(fetchBranch(repository, "branch1")).then(() => { const actions = store.getActions(); expect(actions[0].type).toEqual(FETCH_BRANCH_PENDING); expect(actions[1].type).toEqual(FETCH_BRANCH_SUCCESS); @@ -114,7 +113,7 @@ describe("branches", () => { }); const store = mockStore({}); - return store.dispatch(fetchBranchByName(URL, "branch2")).then(() => { + return store.dispatch(fetchBranch(repository, "branch2")).then(() => { const actions = store.getActions(); expect(actions[0].type).toEqual(FETCH_BRANCH_PENDING); expect(actions[1].type).toEqual(FETCH_BRANCH_FAILURE); @@ -149,51 +148,60 @@ describe("branches", () => { const oldState = { "hitchhiker/heartOfGold": [branch3] }; - const newState = reducer(oldState, action); expect(newState[key]).toContain(branch1); expect(newState[key]).toContain(branch2); - expect(newState["hitchhiker/heartOfGold"]).toContain(branch3); }); it("should update state according to FETCH_BRANCH_SUCCESS action", () => { - const newState = reducer({}, fetchBranchSuccess(branch3)); - expect(newState["branch3"]).toBe(branch3); + const newState = reducer({}, fetchBranchSuccess(repository, branch3)); + expect(newState["foo/bar"]).toEqual([branch3]); }); it("should not delete existing branch from state", () => { const oldState = { - branch1 + "foo/bar": [branch1] }; - - const newState = reducer(oldState, fetchBranchSuccess(branch2)); - expect(newState["branch1"]).toBe(branch1); - expect(newState["branch2"]).toBe(branch2); + const newState = reducer(oldState, fetchBranchSuccess(repository, branch2)); + expect(newState["foo/bar"]).toEqual([branch1, branch2]); }); it("should update required branch from state", () => { const oldState = { - branch1 + "foo/bar": [branch1] }; - const newBranch1 = { name: "branch1", revision: "revision2" }; - - const newState = reducer(oldState, fetchBranchSuccess(newBranch1)); - expect(newState["branch1"]).not.toBe(branch1); - expect(newState["branch1"]).toBe(newBranch1); + const newState = reducer(oldState, fetchBranchSuccess(repository, newBranch1)); + expect(newState["foo/bar"]).toEqual([newBranch1]); }); it("should update required branch from state and keeps old repo", () => { const oldState = { - repo1: { - branch1 - } + "ns/one": [branch1] }; - const repo2 = { repo2: { branch3 } }; - const newState = reducer(oldState, fetchBranchSuccess(repo2, branch2)); - expect(newState["repo1"]).toBe({ branch1 }); - expect(newState["repo2"]).toBe({ branch2, branch3 }); + const newState = reducer(oldState, fetchBranchSuccess(repository, branch3)); + expect(newState["ns/one"]).toEqual([branch1]); + expect(newState["foo/bar"]).toEqual([branch3]); + }); + + it("should return the oldState, if action has no payload", () => { + const state = {}; + const newState = reducer(state, {type: FETCH_BRANCH_SUCCESS}); + expect(newState).toBe(state); + }); + + it("should return the oldState, if payload has no branch", () => { + const action = { + type: FETCH_BRANCH_SUCCESS, + payload: { + repository + }, + itemId: "foo/bar/" + }; + const state = {}; + const newState = reducer(state, action); + expect(newState).toBe(state); }); }); diff --git a/scm-ui/yarn.lock b/scm-ui/yarn.lock index 5b6ec89ef3..4440b28f6c 100644 --- a/scm-ui/yarn.lock +++ b/scm-ui/yarn.lock @@ -698,9 +698,10 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.26": - version "0.0.26" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.26.tgz#4676a7079b781b33fa1989c6643205c3559b1f66" +"@scm-manager/ui-bundler@^0.0.27": + version "0.0.27" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.27.tgz#3ed2c7826780b9a1a9ea90464332640cfb5d54b5" + integrity sha512-cBU1xq6gDy1Vw9AGOzsR763+JmBeraTaC/KQfxT3I6XyZJ2brIfG1m5QYcAcHWvDxq3mYMogpI5rfShw14L4/w== dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" From 81d25e74eb4dca5a9c5c89ab5ce19823a9e7cfca Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Tue, 2 Apr 2019 17:45:25 +0200 Subject: [PATCH 26/79] applied simplified brunchdetail design --- scm-ui/public/locales/de/repos.json | 4 +-- scm-ui/public/locales/en/repos.json | 4 +-- .../{BranchDetailTable.js => BranchDetail.js} | 30 +++++-------------- .../repos/branches/containers/BranchView.js | 4 +-- 4 files changed, 12 insertions(+), 30 deletions(-) rename scm-ui/src/repos/branches/components/{BranchDetailTable.js => BranchDetail.js} (59%) diff --git a/scm-ui/public/locales/de/repos.json b/scm-ui/public/locales/de/repos.json index 3a90f9080a..f3fe2d162e 100644 --- a/scm-ui/public/locales/de/repos.json +++ b/scm-ui/public/locales/de/repos.json @@ -61,9 +61,7 @@ } }, "branch": { - "name": "Name", - "repository": "Repository", - "actions": "Aktionen", + "name": "Name:", "commits": "Commits", "sources": "Sources" }, diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 6adede7b82..ac5cbfe6e1 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -61,9 +61,7 @@ } }, "branch": { - "name": "Name", - "repository": "Repository", - "actions": "Actions", + "name": "Name:", "commits": "Commits", "sources": "Sources" }, diff --git a/scm-ui/src/repos/branches/components/BranchDetailTable.js b/scm-ui/src/repos/branches/components/BranchDetail.js similarity index 59% rename from scm-ui/src/repos/branches/components/BranchDetailTable.js rename to scm-ui/src/repos/branches/components/BranchDetail.js index 27c4907a53..8941e0abef 100644 --- a/scm-ui/src/repos/branches/components/BranchDetailTable.js +++ b/scm-ui/src/repos/branches/components/BranchDetail.js @@ -21,31 +21,17 @@ const styles = { } }; -class BranchDetailTable extends React.Component { +class BranchDetail extends React.Component { render() { const { repository, branch, t } = this.props; return ( -
{t("branch.name")}{t("branch.name")} {branch.name} {this.renderDefaultBranch()}
+ {t("branch.repository")} - + {repository.name}
{t("branch.actions")}{t("branch.actions")}
- {t("branch.repository")} - {t("branch.repository")} {repository.name}
{this.renderLink(to, branch.name, branch.defaultBranch)}
{this.renderLink(to, branch.name, branch.defaultBranch)}
- - - - - - - - - - - - - - -
{t("branch.name")} - {branch.name} {this.renderDefaultBranch()} -
{t("branch.repository")}{repository.name}
{t("branch.actions")} - -
+
+
{t("branch.name")} {branch.name} {this.renderDefaultBranch()}
+
+ +
+
); } @@ -62,4 +48,4 @@ class BranchDetailTable extends React.Component { } } -export default injectSheet(styles)(translate("repos")(BranchDetailTable)); +export default injectSheet(styles)(translate("repos")(BranchDetail)); diff --git a/scm-ui/src/repos/branches/containers/BranchView.js b/scm-ui/src/repos/branches/containers/BranchView.js index 959c4519d0..542cbd67d2 100644 --- a/scm-ui/src/repos/branches/containers/BranchView.js +++ b/scm-ui/src/repos/branches/containers/BranchView.js @@ -1,6 +1,6 @@ // @flow import React from "react"; -import BranchDetailTable from "../components/BranchDetailTable"; +import BranchDetail from "../components/BranchDetail"; import { ExtensionPoint } from "@scm-manager/ui-extensions"; import type { Repository, Branch } from "@scm-manager/ui-types"; import { connect } from "react-redux"; @@ -54,7 +54,7 @@ class BranchView extends React.Component { return (
- +
Date: Wed, 3 Apr 2019 08:52:29 +0200 Subject: [PATCH 27/79] added defaultbranchtag component --- scm-ui/public/locales/de/repos.json | 3 +- scm-ui/public/locales/en/repos.json | 3 +- .../repos/branches/components/BranchDetail.js | 32 ++++------------- .../repos/branches/components/BranchRow.js | 33 +++++------------ .../branches/components/DefaultBranchTag.js | 35 +++++++++++++++++++ 5 files changed, 55 insertions(+), 51 deletions(-) create mode 100644 scm-ui/src/repos/branches/components/DefaultBranchTag.js diff --git a/scm-ui/public/locales/de/repos.json b/scm-ui/public/locales/de/repos.json index f3fe2d162e..6170c9021f 100644 --- a/scm-ui/public/locales/de/repos.json +++ b/scm-ui/public/locales/de/repos.json @@ -63,7 +63,8 @@ "branch": { "name": "Name:", "commits": "Commits", - "sources": "Sources" + "sources": "Sources", + "defaultTag": "Default" }, "changesets": { "errorTitle": "Fehler", diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index ac5cbfe6e1..4eeddea8b2 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -63,7 +63,8 @@ "branch": { "name": "Name:", "commits": "Commits", - "sources": "Sources" + "sources": "Sources", + "defaultTag": "Default" }, "changesets": { "errorTitle": "Error", diff --git a/scm-ui/src/repos/branches/components/BranchDetail.js b/scm-ui/src/repos/branches/components/BranchDetail.js index 8941e0abef..86b142d9da 100644 --- a/scm-ui/src/repos/branches/components/BranchDetail.js +++ b/scm-ui/src/repos/branches/components/BranchDetail.js @@ -2,23 +2,14 @@ import React from "react"; import type { Repository, Branch } from "@scm-manager/ui-types"; import { translate } from "react-i18next"; -import injectSheet from "react-jss"; -import classNames from "classnames"; import BranchButtonGroup from "./BranchButtonGroup"; +import DefaultBranchTag from "./DefaultBranchTag"; type Props = { repository: Repository, branch: Branch, // context props - t: string => string, - classes: any -}; - -const styles = { - tag: { - marginLeft: "0.75rem", - verticalAlign: "inherit" - } + t: string => string }; class BranchDetail extends React.Component { @@ -27,25 +18,16 @@ class BranchDetail extends React.Component { return (
-
{t("branch.name")} {branch.name} {this.renderDefaultBranch()}
+
+ {t("branch.name")} {branch.name}{" "} + +
); } - - renderDefaultBranch() { - const { branch, classes } = this.props; - - let defaultLabel = null; - if (branch.defaultBranch) { - defaultLabel = ( - Default - ); - } - return defaultLabel; - } } -export default injectSheet(styles)(translate("repos")(BranchDetail)); +export default translate("repos")(BranchDetail); diff --git a/scm-ui/src/repos/branches/components/BranchRow.js b/scm-ui/src/repos/branches/components/BranchRow.js index d8f70575b3..025e8f2742 100644 --- a/scm-ui/src/repos/branches/components/BranchRow.js +++ b/scm-ui/src/repos/branches/components/BranchRow.js @@ -2,33 +2,18 @@ import React from "react"; import { Link } from "react-router-dom"; import type { Branch } from "@scm-manager/ui-types"; -import injectSheet from "react-jss"; -import classNames from "classnames"; +import DefaultBranchTag from "./DefaultBranchTag"; type Props = { baseUrl: string, - branch: Branch, - classes: any -}; - -const styles = { - tag: { - marginLeft: "0.75rem", - verticalAlign: "inherit" - } + branch: Branch }; class BranchRow extends React.Component { renderLink(to: string, label: string, defaultBranch?: boolean) { - const { classes } = this.props; - - let showLabel = null; - if (defaultBranch) { - showLabel = Default; - } return ( - {label} {showLabel} + {label} ); } @@ -36,12 +21,12 @@ class BranchRow extends React.Component { render() { const { baseUrl, branch } = this.props; const to = `${baseUrl}/${encodeURIComponent(branch.name)}/info`; - return ( - - {this.renderLink(to, branch.name, branch.defaultBranch)} - - ); + return ( + + {this.renderLink(to, branch.name, branch.defaultBranch)} + + ); } } -export default injectSheet(styles)(BranchRow); +export default BranchRow; diff --git a/scm-ui/src/repos/branches/components/DefaultBranchTag.js b/scm-ui/src/repos/branches/components/DefaultBranchTag.js new file mode 100644 index 0000000000..18be6d21f8 --- /dev/null +++ b/scm-ui/src/repos/branches/components/DefaultBranchTag.js @@ -0,0 +1,35 @@ +//@flow +import React from "react"; +import injectSheet from "react-jss"; +import classNames from "classnames"; +import { translate } from "react-i18next"; + +type Props = { + defaultBranch?: boolean, + classes: any, + t: string => string +}; + +const styles = { + tag: { + marginLeft: "0.75rem", + verticalAlign: "inherit" + } +}; + +class DefaultBranchTag extends React.Component { + render() { + const { defaultBranch, classes, t } = this.props; + + if (defaultBranch) { + return ( + + {t("branch.defaultTag")} + + ); + } + return null; + } +} + +export default injectSheet(styles)(translate("repos")(DefaultBranchTag)); From 148f05daeeb3eeb5087923c70af90f515bb6922e Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Wed, 3 Apr 2019 10:11:43 +0200 Subject: [PATCH 28/79] review changes have been implemented: integrated createLink functionalities in BranchButtonGroup, outsourced orderBraches and wrote unit tests --- .../branches/components/BranchButtonGroup.js | 9 +- .../repos/branches/containers/BranchView.js | 8 +- .../branches/containers/BranchesOverview.js | 32 +-- scm-ui/src/repos/branches/modules/branches.js | 235 +++++++++--------- .../repos/branches/modules/branches.test.js | 59 ++++- 5 files changed, 189 insertions(+), 154 deletions(-) diff --git a/scm-ui/src/repos/branches/components/BranchButtonGroup.js b/scm-ui/src/repos/branches/components/BranchButtonGroup.js index dc170963e9..424d4fd156 100644 --- a/scm-ui/src/repos/branches/components/BranchButtonGroup.js +++ b/scm-ui/src/repos/branches/components/BranchButtonGroup.js @@ -3,7 +3,6 @@ import React from "react"; import type { Repository, Branch } from "@scm-manager/ui-types"; import { ButtonGroup, Button } from "@scm-manager/ui-components"; import { translate } from "react-i18next"; -import { createChangesetLink, createSourcesLink } from "../modules/branches"; type Props = { repository: Repository, @@ -17,8 +16,12 @@ class BranchButtonGroup extends React.Component { render() { const { repository, branch, t } = this.props; - const changesetLink = createChangesetLink(repository, branch); - const sourcesLink = createSourcesLink(repository, branch); + const changesetLink = `/repo/${repository.namespace}/${ + repository.name + }/branch/${encodeURIComponent(branch.name)}/changesets/`; + const sourcesLink = `/repo/${repository.namespace}/${ + repository.name + }/sources/${encodeURIComponent(branch.name)}/`; return ( diff --git a/scm-ui/src/repos/branches/containers/BranchView.js b/scm-ui/src/repos/branches/containers/BranchView.js index 542cbd67d2..5e7fa9610a 100644 --- a/scm-ui/src/repos/branches/containers/BranchView.js +++ b/scm-ui/src/repos/branches/containers/BranchView.js @@ -18,8 +18,8 @@ type Props = { repository: Repository, branchName: string, loading: boolean, - error: Error, - branch: Branch, + error?: Error, + branch?: Branch, // dispatch functions fetchBranch: (repository: Repository, branchName: string) => void, @@ -72,8 +72,8 @@ const mapStateToProps = (state, ownProps) => { const { repository } = ownProps; const branchName = decodeURIComponent(ownProps.match.params.branch); const branch = getBranch(state, repository, branchName); - const loading = isFetchBranchPending(state, branchName); - const error = getFetchBranchFailure(state, branchName); + const loading = isFetchBranchPending(state, repository, branchName); + const error = getFetchBranchFailure(state, repository, branchName); return { repository, branchName, diff --git a/scm-ui/src/repos/branches/containers/BranchesOverview.js b/scm-ui/src/repos/branches/containers/BranchesOverview.js index 4f4ca4a75d..3bc92deab4 100644 --- a/scm-ui/src/repos/branches/containers/BranchesOverview.js +++ b/scm-ui/src/repos/branches/containers/BranchesOverview.js @@ -4,7 +4,8 @@ import { fetchBranches, getBranches, getFetchBranchesFailure, - isFetchBranchesPending + isFetchBranchesPending, + orderBranches } from "../modules/branches"; import { connect } from "react-redux"; import type { Branch, Repository } from "@scm-manager/ui-types"; @@ -36,35 +37,6 @@ type Props = { t: string => string }; -// master, default should always be the first one, -// followed by develop the rest should be ordered by its name -export function orderBranches(branches: Branch[]) { - branches.sort((a, b) => { - if (a.defaultBranch && !b.defaultBranch) { - return -20; - } else if (!a.defaultBranch && b.defaultBranch) { - return 20; - } else if (a.name === "master" && b.name !== "master") { - return -10; - } else if (a.name !== "master" && b.name === "master") { - return 10; - } else if (a.name === "default" && b.name !== "default") { - return -10; - } else if (a.name !== "default" && b.name === "default") { - return 10; - } else if (a.name === "develop" && b.name !== "develop") { - return -5; - } else if (a.name !== "develop" && b.name === "develop") { - return 5; - } else if (a.name < b.name) { - return -1; - } else if (a.name > b.name) { - return 1; - } - return 0; - }); -} - class BranchesOverview extends React.Component { componentDidMount() { const { fetchBranches, repository } = this.props; diff --git a/scm-ui/src/repos/branches/modules/branches.js b/scm-ui/src/repos/branches/modules/branches.js index dd542d0183..a5f676eced 100644 --- a/scm-ui/src/repos/branches/modules/branches.js +++ b/scm-ui/src/repos/branches/modules/branches.js @@ -21,41 +21,26 @@ export const FETCH_BRANCH_FAILURE = `${FETCH_BRANCH}_${FAILURE_SUFFIX}`; // Fetching branches -function createIdentifier(repository: Repository) { - return repository.namespace + "/" + repository.name; -} +export function fetchBranches(repository: Repository) { + if (!repository._links.branches) { + return { + type: FETCH_BRANCHES_SUCCESS, + payload: { repository, data: {} }, + itemId: createKey(repository) + }; + } -export function fetchBranchPending( - repository: Repository, - name: string -): Action { - return { - type: FETCH_BRANCH_PENDING, - payload: { repository, name }, - itemId: createIdentifier(repository) + "/" + name - }; -} - -export function fetchBranchSuccess( - repository: Repository, - branch: Branch -): Action { - return { - type: FETCH_BRANCH_SUCCESS, - payload: { repository, branch }, - itemId: createIdentifier(repository) + "/" + branch.name - }; -} - -export function fetchBranchFailure( - repository: Repository, - name: string, - error: Error -): Action { - return { - type: FETCH_BRANCH_FAILURE, - payload: { error, repository, name }, - itemId: createIdentifier(repository) + "/" + name + return function(dispatch: any) { + dispatch(fetchBranchesPending(repository)); + return apiClient + .get(repository._links.branches.href) + .then(response => response.json()) + .then(data => { + dispatch(fetchBranchesSuccess(data, repository)); + }) + .catch(error => { + dispatch(fetchBranchesFailure(repository, error)); + }); }; } @@ -84,38 +69,48 @@ export function fetchBranch( }; } -export function isFetchBranchPending(state: Object, name: string) { - return isPending(state, FETCH_BRANCH, name); -} +// Selectors -export function getFetchBranchFailure(state: Object, name: string) { - return getFailure(state, FETCH_BRANCH, name); -} - -export function fetchBranches(repository: Repository) { - if (!repository._links.branches) { - return { - type: FETCH_BRANCHES_SUCCESS, - payload: { repository, data: {} }, - itemId: createKey(repository) - }; +export function getBranches(state: Object, repository: Repository) { + const key = createKey(repository); + if (state.branches[key]) { + return state.branches[key]; } + return null; +} - return function(dispatch: any) { - dispatch(fetchBranchesPending(repository)); - return apiClient - .get(repository._links.branches.href) - .then(response => response.json()) - .then(data => { - dispatch(fetchBranchesSuccess(data, repository)); - }) - .catch(error => { - dispatch(fetchBranchesFailure(repository, error)); - }); - }; +export function getBranch( + state: Object, + repository: Repository, + name: string +): ?Branch { + const key = createKey(repository); + if (state.branches[key]) { + return state.branches[key].find((b: Branch) => b.name === name); + } + return null; } // Action creators +export function isFetchBranchesPending( + state: Object, + repository: Repository +): boolean { + return isPending(state, FETCH_BRANCHES, createKey(repository)); +} + +export function getFetchBranchesFailure(state: Object, repository: Repository) { + return getFailure(state, FETCH_BRANCHES, createKey(repository)); +} + +export function isFetchBranchPending(state: Object, repository: Repository, name: string) { + return isPending(state, FETCH_BRANCH, createKey(repository) + "/" + name); +} + +export function getFetchBranchFailure(state: Object, repository: Repository, name: string) { + return getFailure(state, FETCH_BRANCH, createKey(repository) + "/" + name); +} + export function fetchBranchesPending(repository: Repository) { return { type: FETCH_BRANCHES_PENDING, @@ -140,8 +135,49 @@ export function fetchBranchesFailure(repository: Repository, error: Error) { }; } +export function fetchBranchPending( + repository: Repository, + name: string +): Action { + return { + type: FETCH_BRANCH_PENDING, + payload: { repository, name }, + itemId: createKey(repository) + "/" + name + }; +} + +export function fetchBranchSuccess( + repository: Repository, + branch: Branch +): Action { + return { + type: FETCH_BRANCH_SUCCESS, + payload: { repository, branch }, + itemId: createKey(repository) + "/" + branch.name + }; +} + +export function fetchBranchFailure( + repository: Repository, + name: string, + error: Error +): Action { + return { + type: FETCH_BRANCH_FAILURE, + payload: { error, repository, name }, + itemId: createKey(repository) + "/" + name + }; +} + // Reducers +function extractBranchesFromPayload(payload: any) { + if (payload._embedded && payload._embedded.branches) { + return payload._embedded.branches; + } + return []; +} + function reduceBranchSuccess(state, repositoryName, newBranch) { const newBranches = []; // we do not use filter, because we try to keep the current order @@ -182,7 +218,7 @@ export default function reducer( return state; } const newBranch = action.payload.branch; - const repositoryName = createIdentifier(action.payload.repository); + const repositoryName = createKey(action.payload.repository); return { ...state, [repositoryName]: reduceBranchSuccess(state, repositoryName, newBranch) @@ -192,59 +228,36 @@ export default function reducer( } } -function extractBranchesFromPayload(payload: any) { - if (payload._embedded && payload._embedded.branches) { - return payload._embedded.branches; - } - return []; -} - -// Selectors - -export function getBranches(state: Object, repository: Repository) { - const key = createKey(repository); - if (state.branches[key]) { - return state.branches[key]; - } - return null; -} - -export function getBranch( - state: Object, - repository: Repository, - name: string -): ?Branch { - const key = createKey(repository); - if (state.branches[key]) { - return state.branches[key].find((b: Branch) => b.name === name); - } - return null; -} - -export function isFetchBranchesPending( - state: Object, - repository: Repository -): boolean { - return isPending(state, FETCH_BRANCHES, createKey(repository)); -} - -export function getFetchBranchesFailure(state: Object, repository: Repository) { - return getFailure(state, FETCH_BRANCHES, createKey(repository)); -} - function createKey(repository: Repository): string { const { namespace, name } = repository; return `${namespace}/${name}`; } -export function createChangesetLink(repository: Repository, branch: Branch) { - return `/repo/${repository.namespace}/${ - repository.name - }/branch/${encodeURIComponent(branch.name)}/changesets/`; -} - -export function createSourcesLink(repository: Repository, branch: Branch) { - return `/repo/${repository.namespace}/${ - repository.name - }/sources/${encodeURIComponent(branch.name)}/`; +// master, default should always be the first one, +// followed by develop the rest should be ordered by its name +export function orderBranches(branches: Branch[]) { + branches.sort((a, b) => { + if (a.defaultBranch && !b.defaultBranch) { + return -20; + } else if (!a.defaultBranch && b.defaultBranch) { + return 20; + } else if (a.name === "master" && b.name !== "master") { + return -10; + } else if (a.name !== "master" && b.name === "master") { + return 10; + } else if (a.name === "default" && b.name !== "default") { + return -10; + } else if (a.name !== "default" && b.name === "default") { + return 10; + } else if (a.name === "develop" && b.name !== "develop") { + return -5; + } else if (a.name !== "develop" && b.name === "develop") { + return 5; + } else if (a.name < b.name) { + return -1; + } else if (a.name > b.name) { + return 1; + } + return 0; + }); } diff --git a/scm-ui/src/repos/branches/modules/branches.test.js b/scm-ui/src/repos/branches/modules/branches.test.js index 30b81975d6..8db4f6f83a 100644 --- a/scm-ui/src/repos/branches/modules/branches.test.js +++ b/scm-ui/src/repos/branches/modules/branches.test.js @@ -16,7 +16,8 @@ import reducer, { getBranch, getBranches, getFetchBranchesFailure, - isFetchBranchesPending + isFetchBranchesPending, + orderBranches } from "./branches"; const namespace = "foo"; @@ -34,7 +35,18 @@ const repository = { const branch1 = { name: "branch1", revision: "revision1" }; const branch2 = { name: "branch2", revision: "revision2" }; -const branch3 = { name: "branch3", revision: "revision3" }; +const branch3 = { name: "branch3", revision: "revision3", defaultBranch: true }; +const defaultBranch = { + name: "default", + revision: "revision4", + defaultBranch: false +}; +const developBranch = { + name: "develop", + revision: "revision5", + defaultBranch: false +}; +const masterBranch = { name: "master", revision: "revision6", defaultBranch: false }; describe("branches", () => { describe("fetch branches", () => { @@ -163,7 +175,10 @@ describe("branches", () => { const oldState = { "foo/bar": [branch1] }; - const newState = reducer(oldState, fetchBranchSuccess(repository, branch2)); + const newState = reducer( + oldState, + fetchBranchSuccess(repository, branch2) + ); expect(newState["foo/bar"]).toEqual([branch1, branch2]); }); @@ -172,7 +187,10 @@ describe("branches", () => { "foo/bar": [branch1] }; const newBranch1 = { name: "branch1", revision: "revision2" }; - const newState = reducer(oldState, fetchBranchSuccess(repository, newBranch1)); + const newState = reducer( + oldState, + fetchBranchSuccess(repository, newBranch1) + ); expect(newState["foo/bar"]).toEqual([newBranch1]); }); @@ -180,14 +198,17 @@ describe("branches", () => { const oldState = { "ns/one": [branch1] }; - const newState = reducer(oldState, fetchBranchSuccess(repository, branch3)); + const newState = reducer( + oldState, + fetchBranchSuccess(repository, branch3) + ); expect(newState["ns/one"]).toEqual([branch1]); expect(newState["foo/bar"]).toEqual([branch3]); }); it("should return the oldState, if action has no payload", () => { const state = {}; - const newState = reducer(state, {type: FETCH_BRANCH_SUCCESS}); + const newState = reducer(state, { type: FETCH_BRANCH_SUCCESS }); expect(newState).toBe(state); }); @@ -272,4 +293,30 @@ describe("branches", () => { expect(getFetchBranchesFailure({}, repository)).toBeUndefined(); }); }); + + describe("sort branches", () => { + it("should return branches", () => { + let branches = [branch1, branch2]; + orderBranches(branches); + expect(branches).toEqual([branch1, branch2]); + }); + + it("should return defaultBranch first", () => { + let branches = [branch1, branch2, branch3]; + orderBranches(branches); + expect(branches).toEqual([branch3, branch1, branch2]); + }); + + it("should order special branches as follows: master > default > develop", () => { + let branches = [defaultBranch, developBranch, masterBranch]; + orderBranches(branches); + expect(branches).toEqual([masterBranch, defaultBranch, developBranch]); + }); + + it("should order special branches but starting with defaultBranch", () => { + let branches = [masterBranch, developBranch, defaultBranch, branch3]; + orderBranches(branches); + expect(branches).toEqual([branch3, masterBranch, defaultBranch, developBranch]); + }); + }); }); From d4e996dde2dc57684de7f2a6860760db1973cb9e Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Wed, 3 Apr 2019 13:12:07 +0200 Subject: [PATCH 29/79] started creating branch form --- .../repos/branches/components/BranchForm.js | 95 +++++++++++++++- .../repos/branches/containers/CreateBranch.js | 98 ++++++++++++++++- scm-ui/src/repos/branches/modules/branches.js | 104 +++++++++++++++--- 3 files changed, 274 insertions(+), 23 deletions(-) diff --git a/scm-ui/src/repos/branches/components/BranchForm.js b/scm-ui/src/repos/branches/components/BranchForm.js index 614a719338..fa14192c07 100644 --- a/scm-ui/src/repos/branches/components/BranchForm.js +++ b/scm-ui/src/repos/branches/components/BranchForm.js @@ -1,16 +1,103 @@ -//@flow +// @flow import React from "react"; +import { translate } from "react-i18next"; +import type { Repository, Branch } from "@scm-manager/ui-types"; +import { + Select, + InputField, + SubmitButton, + validation as validator +} from "@scm-manager/ui-components"; -type Props = {}; +type Props = { + submitForm: Branch => void, + repository: Repository, + branches: Branch[], + loading?: boolean, + t: string => string +}; + +type State = { + source?: string, + name?: string, + nameValidationError: boolean +}; + +class BranchForm extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { + nameValidationError: false + }; + } + + isValid = () => { + return true; //TODO + }; + + submit = (event: Event) => { + event.preventDefault(); + if (this.isValid()) { + this.props.submitForm(this.state.branch); + } + }; -class CreateBranch extends React.Component { render() { + const { t, branches } = this.props; + const { loading } = this.state; + const options = branches.map(branch => ({ + label: branch.name, + value: branch.name + })); + return ( <> -

Form placeholder

+
+
+
+ From be9f1aeec0ee27d842d97b38d363ec5f2693c3c2 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 8 Apr 2019 11:24:48 +0200 Subject: [PATCH 49/79] update react to 16.8 --- .../packages/ui-components/package.json | 6 ++--- .../packages/ui-components/yarn.lock | 23 ++++++++++------ scm-ui/package.json | 4 +-- scm-ui/yarn.lock | 26 ++++++++++++++++++- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/scm-ui-components/packages/ui-components/package.json b/scm-ui-components/packages/ui-components/package.json index c1a179315c..991142e338 100644 --- a/scm-ui-components/packages/ui-components/package.json +++ b/scm-ui-components/packages/ui-components/package.json @@ -30,9 +30,9 @@ "@scm-manager/ui-types": "2.0.0-SNAPSHOT", "classnames": "^2.2.6", "moment": "^2.22.2", - "react": "^16.5.2", + "react": "^16.8.6", + "react-dom": "^16.8.6", "react-diff-view": "^1.8.1", - "react-dom": "^16.5.2", "react-i18next": "^7.11.0", "react-jss": "^8.6.1", "react-router-dom": "^4.3.1", @@ -63,4 +63,4 @@ ] ] } -} \ No newline at end of file +} diff --git a/scm-ui-components/packages/ui-components/yarn.lock b/scm-ui-components/packages/ui-components/yarn.lock index cc21af6b2e..bce98a17e4 100644 --- a/scm-ui-components/packages/ui-components/yarn.lock +++ b/scm-ui-components/packages/ui-components/yarn.lock @@ -6623,14 +6623,14 @@ react-dom@^16.4.2: prop-types "^15.6.2" scheduler "^0.10.0" -react-dom@^16.5.2: - version "16.5.2" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.5.2.tgz#b69ee47aa20bab5327b2b9d7c1fe2a30f2cfa9d7" +react-dom@^16.8.6: + version "16.8.6" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.6.tgz#71d6303f631e8b0097f56165ef608f051ff6e10f" dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - schedule "^0.5.0" + scheduler "^0.13.6" react-i18next@^7.11.0: version "7.13.0" @@ -6755,14 +6755,14 @@ react@^16.4.2: prop-types "^15.6.2" scheduler "^0.10.0" -react@^16.5.2: - version "16.5.2" - resolved "https://registry.yarnpkg.com/react/-/react-16.5.2.tgz#19f6b444ed139baa45609eee6dc3d318b3895d42" +react@^16.8.6: + version "16.8.6" + resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe" dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - schedule "^0.5.0" + scheduler "^0.13.6" read-only-stream@^2.0.0: version "2.0.0" @@ -7229,6 +7229,13 @@ scheduler@^0.10.0: loose-envify "^1.1.0" object-assign "^4.1.1" +scheduler@^0.13.6: + version "0.13.6" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.6.tgz#466a4ec332467b31a91b9bf74e5347072e4cd889" + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + select@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" diff --git a/scm-ui/package.json b/scm-ui/package.json index 78c7b0e684..c8eef541d7 100644 --- a/scm-ui/package.json +++ b/scm-ui/package.json @@ -20,9 +20,9 @@ "moment": "^2.22.2", "node-sass": "^4.9.3", "postcss-easy-import": "^3.0.0", - "react": "^16.4.2", + "react": "^16.8.6", "react-diff-view": "^1.8.1", - "react-dom": "^16.4.2", + "react-dom": "^16.8.6", "react-i18next": "^7.9.0", "react-jss": "^8.6.0", "react-redux": "^5.0.7", diff --git a/scm-ui/yarn.lock b/scm-ui/yarn.lock index 4440b28f6c..00ae02a10a 100644 --- a/scm-ui/yarn.lock +++ b/scm-ui/yarn.lock @@ -701,7 +701,6 @@ "@scm-manager/ui-bundler@^0.0.27": version "0.0.27" resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.27.tgz#3ed2c7826780b9a1a9ea90464332640cfb5d54b5" - integrity sha512-cBU1xq6gDy1Vw9AGOzsR763+JmBeraTaC/KQfxT3I6XyZJ2brIfG1m5QYcAcHWvDxq3mYMogpI5rfShw14L4/w== dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -6921,6 +6920,15 @@ react-dom@^16.4.2: prop-types "^15.6.2" schedule "^0.5.0" +react-dom@^16.8.6: + version "16.8.6" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.6.tgz#71d6303f631e8b0097f56165ef608f051ff6e10f" + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.13.6" + react-i18next@^7.9.0: version "7.13.0" resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-7.13.0.tgz#a6f64fd749215ec70400f90da6cbde2a9c5b1588" @@ -7051,6 +7059,15 @@ react@^16.4.2: prop-types "^15.6.2" schedule "^0.5.0" +react@^16.8.6: + version "16.8.6" + resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe" + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.13.6" + read-cache@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" @@ -7563,6 +7580,13 @@ schedule@^0.5.0: dependencies: object-assign "^4.1.1" +scheduler@^0.13.6: + version "0.13.6" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.6.tgz#466a4ec332467b31a91b9bf74e5347072e4cd889" + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + scss-tokenizer@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" From 2c82026e86359ec1feeb0c022751c7b6358596c6 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 8 Apr 2019 15:16:37 +0200 Subject: [PATCH 50/79] refactoring branches state --- scm-ui/src/repos/branches/modules/branches.js | 110 +++++----- .../repos/branches/modules/branches.test.js | 191 ++++++++++++------ 2 files changed, 186 insertions(+), 115 deletions(-) diff --git a/scm-ui/src/repos/branches/modules/branches.js b/scm-ui/src/repos/branches/modules/branches.js index ffe257a94c..eb1b2bb665 100644 --- a/scm-ui/src/repos/branches/modules/branches.js +++ b/scm-ui/src/repos/branches/modules/branches.js @@ -3,7 +3,6 @@ import { FAILURE_SUFFIX, PENDING_SUFFIX, SUCCESS_SUFFIX, - default as types } from "../../../modules/types"; import { apiClient } from "@scm-manager/ui-components"; import type { Action, Branch, Repository } from "@scm-manager/ui-types"; @@ -103,15 +102,24 @@ export function createBranch( // Selectors export function getBranches(state: Object, repository: Repository) { - const key = createKey(repository); - if (state.branches[key]) { - return state.branches[key]; + const repoState = getRepoState(state, repository); + if (repoState && repoState.list) { + return repoState.list._embedded.branches.map(name => repoState.byName[name]); } - return null; } -export const isPermittedToCreateBranches = (state: Object): boolean => { - return false; // TODO +function getRepoState(state: Object, repository: Repository) { + const key = createKey(repository); + const repoState = state.branches[key]; + if (repoState && repoState.byName) { + return repoState; + } +} + +export const isPermittedToCreateBranches = (state: Object, repository: Repository): boolean => { + const repoState = getRepoState(state, repository); + return !!(repoState && repoState.list && repoState.list._links && repoState.list._links.create); + }; export function getBranch( @@ -119,11 +127,10 @@ export function getBranch( repository: Repository, name: string ): ?Branch { - const key = createKey(repository); - if (state.branches[key]) { - return state.branches[key].find((b: Branch) => b.name === name); + const repoState = getRepoState(state, repository); + if (repoState) { + return repoState.byName[name]; } - return null; } // Action creators @@ -252,58 +259,61 @@ export function fetchBranchFailure( // Reducers -function extractBranchesFromPayload(payload: any) { - if (payload._embedded && payload._embedded.branches) { - return payload._embedded.branches; - } - return []; -} +const reduceByBranchesSuccess = (state, payload) => { + const repository = payload.repository; + const response = payload.data; -function reduceBranchSuccess(state, repositoryName, newBranch) { - const newBranches = []; - // we do not use filter, because we try to keep the current order - let found = false; - for (const branch of state[repositoryName] || []) { - if (branch.name === newBranch.name) { - newBranches.push(newBranch); - found = true; - } else { - newBranches.push(branch); + const key = createKey(repository); + const repoState = state[key] || {}; + const byName = repoState.byName || {}; + repoState.byName = byName; + + const branches = response._embedded.branches; + const names = branches.map(b => b.name); + response._embedded.branches = names; + for (let branch of branches) { + byName[branch.name] = branch; + } + return { + [key]: { + list: response, + byName } - } - if (!found) { - newBranches.push(newBranch); - } - return newBranches; -} + }; +}; -type State = { [string]: Branch[] }; +const reduceByBranchSuccess = (state, payload) => { + const repository = payload.repository; + const branch = payload.branch; + + const key = createKey(repository); + + const repoState = state[key] || {}; + + const byName = repoState.byName || {}; + byName[branch.name] = branch; + + repoState.byName = byName; + + return { + ...state, + [key]: repoState + }; +}; export default function reducer( - state: State = {}, + state: {}, action: Action = { type: "UNKNOWN" } -): State { +): Object { if (!action.payload) { return state; } const payload = action.payload; switch (action.type) { case FETCH_BRANCHES_SUCCESS: - const key = createKey(payload.repository); - return { - ...state, - [key]: extractBranchesFromPayload(payload.data) - }; + return reduceByBranchesSuccess(state, payload); case FETCH_BRANCH_SUCCESS: - if (!action.payload.repository || !action.payload.branch) { - return state; - } - const newBranch = action.payload.branch; - const repositoryName = createKey(action.payload.repository); - return { - ...state, - [repositoryName]: reduceBranchSuccess(state, repositoryName, newBranch) - }; + return reduceByBranchSuccess(state, payload); default: return state; } diff --git a/scm-ui/src/repos/branches/modules/branches.test.js b/scm-ui/src/repos/branches/modules/branches.test.js index 4e5c1d96a9..3f81d8bb5b 100644 --- a/scm-ui/src/repos/branches/modules/branches.test.js +++ b/scm-ui/src/repos/branches/modules/branches.test.js @@ -22,7 +22,7 @@ import reducer, { isFetchBranchesPending, createBranch, isCreateBranchPending, - getCreateBranchFailure + getCreateBranchFailure, isPermittedToCreateBranches } from "./branches"; const namespace = "foo"; @@ -192,6 +192,14 @@ describe("branches", () => { const branches = { _embedded: { branches: [branch1, branch2] + }, + _links: { + self: { + href: "/self" + }, + create: { + href: "/create" + } } }; const action = { @@ -202,82 +210,96 @@ describe("branches", () => { } }; - it("should update state according to successful fetch", () => { + it("should store the branches", () => { const newState = reducer({}, action); - expect(newState).toBeDefined(); - expect(newState[key]).toBeDefined(); - expect(newState[key]).toContain(branch1); - expect(newState[key]).toContain(branch2); + const repoState = newState["foo/bar"]; + + expect(repoState.list._links.create.href).toEqual("/create"); + expect(repoState.list._embedded.branches).toEqual([ "branch1", "branch2" ]); + + expect(repoState.byName.branch1).toEqual(branch1); + expect(repoState.byName.branch2).toEqual(branch2); }); - it("should not delete existing branches from state", () => { - const oldState = { - "hitchhiker/heartOfGold": [branch3] + it("should store a single branch", () => { + const newState = reducer({}, fetchBranchSuccess(repository, branch1)); + const repoState = newState["foo/bar"]; + + expect(repoState.list).toBeUndefined(); + expect(repoState.byName.branch1).toEqual(branch1); + }); + + it("should add a single branch", () => { + const state = { + "foo/bar": { + list: { + _links: { + + }, + _embedded: { + branches: ["branch1"] + } + }, + byName: { + "branch1": branch1 + } + } }; - const newState = reducer(oldState, action); - expect(newState[key]).toContain(branch1); - expect(newState[key]).toContain(branch2); - expect(newState["hitchhiker/heartOfGold"]).toContain(branch3); + const newState = reducer(state, fetchBranchSuccess(repository, branch2)); + const repoState = newState["foo/bar"]; + const byName = repoState.byName; + + expect(repoState.list._embedded.branches).toEqual(["branch1"]); + expect(byName.branch1).toEqual(branch1); + expect(byName.branch2).toEqual(branch2); }); - it("should update state according to FETCH_BRANCH_SUCCESS action", () => { - const newState = reducer({}, fetchBranchSuccess(repository, branch3)); - expect(newState["foo/bar"]).toEqual([branch3]); - }); - - it("should not delete existing branch from state", () => { - const oldState = { - "foo/bar": [branch1] + it("should not overwrite non related repositories", () => { + const state = { + "scm/core": { + byName: { + "branch1": branch1 + } + } }; - const newState = reducer( - oldState, - fetchBranchSuccess(repository, branch2) - ); - expect(newState["foo/bar"]).toEqual([branch1, branch2]); + const newState = reducer(state, fetchBranchSuccess(repository, branch1)); + const byName = newState["scm/core"].byName; + + expect(byName.branch1).toEqual(branch1); }); - it("should update required branch from state", () => { - const oldState = { - "foo/bar": [branch1] + it("should overwrite existing branch", () => { + const state = { + "foo/bar": { + byName: { + "branch1": { + "name": "branch1", + "revision": "xyz" + } + } + } }; - const newBranch1 = { name: "branch1", revision: "revision2" }; - const newState = reducer( - oldState, - fetchBranchSuccess(repository, newBranch1) - ); - expect(newState["foo/bar"]).toEqual([newBranch1]); + const newState = reducer(state, fetchBranchSuccess(repository, branch1)); + const byName = newState["foo/bar"].byName; + + expect(byName.branch1.revision).toEqual("revision1"); }); - it("should update required branch from state and keeps old repo", () => { - const oldState = { - "ns/one": [branch1] + it("should not overwrite existing branches", () => { + const state = { + "foo/bar": { + byName: { + branch3 + } + } }; - const newState = reducer( - oldState, - fetchBranchSuccess(repository, branch3) - ); - expect(newState["ns/one"]).toEqual([branch1]); - expect(newState["foo/bar"]).toEqual([branch3]); - }); - it("should return the oldState, if action has no payload", () => { - const state = {}; - const newState = reducer(state, { type: FETCH_BRANCH_SUCCESS }); - expect(newState).toBe(state); - }); - - it("should return the oldState, if payload has no branch", () => { - const action = { - type: FETCH_BRANCH_SUCCESS, - payload: { - repository - }, - itemId: "foo/bar/" - }; - const state = {}; const newState = reducer(state, action); - expect(newState).toBe(state); + expect(newState["foo/bar"].byName.branch1).toEqual(branch1); + expect(newState["foo/bar"].byName.branch2).toEqual(branch2); + expect(newState["foo/bar"].byName.branch3).toEqual(branch3); }); + }); describe("branch selectors", () => { @@ -285,7 +307,20 @@ describe("branches", () => { const state = { branches: { - [key]: [branch1, branch2] + "foo/bar": { + list: { + _links: { + + }, + _embedded: { + branches: ["branch1", "branch2"] + } + }, + byName: { + "branch1": branch1, + "branch2": branch2 + } + } } }; @@ -312,9 +347,9 @@ describe("branches", () => { expect(one).toBe(two); }); - it("should return null, if no branches for the repository available", () => { + it("should return undefined, if no branches for the repository available", () => { const branches = getBranches({ branches: {} }, repository); - expect(branches).toBeNull(); + expect(branches).toBeUndefined(); }); it("should return single branch by name", () => { @@ -333,6 +368,32 @@ describe("branches", () => { expect(branch).toBeUndefined(); }); + it("should return true if the branches list contains the create link", () => { + const stateWithLink = { + branches: { + "foo/bar": { + ...state.branches["foo/bar"], + list: { + ...state.branches["foo/bar"].list, + _links: { + create: { + href: "http://create-it" + } + } + }, + } + } + }; + + const permitted = isPermittedToCreateBranches(stateWithLink, repository); + expect(permitted).toBe(true); + }); + + it("should return false if the create link is missing", () => { + const permitted = isPermittedToCreateBranches(state, repository); + expect(permitted).toBe(false); + }); + it("should return error if fetching branches failed", () => { const state = { failure: { @@ -374,7 +435,7 @@ describe("branches", () => { expect(getCreateBranchFailure(state)).toEqual(error); }); - it("shouls return undefined when create branch did not fail", () => { + it("should return undefined when create branch did not fail", () => { expect(getCreateBranchFailure({})).toBe(undefined); }); }); From f1259977d3957bfb67eb662b8c52fc415558d37b Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Tue, 9 Apr 2019 12:46:52 +0200 Subject: [PATCH 51/79] fix branches tests --- scm-ui/src/repos/branches/modules/branches.js | 19 ++++-- .../repos/branches/modules/branches.test.js | 68 ++++++++++--------- 2 files changed, 50 insertions(+), 37 deletions(-) diff --git a/scm-ui/src/repos/branches/modules/branches.js b/scm-ui/src/repos/branches/modules/branches.js index eb1b2bb665..3979b210d9 100644 --- a/scm-ui/src/repos/branches/modules/branches.js +++ b/scm-ui/src/repos/branches/modules/branches.js @@ -2,7 +2,7 @@ import { FAILURE_SUFFIX, PENDING_SUFFIX, - SUCCESS_SUFFIX, + SUCCESS_SUFFIX } from "../../../modules/types"; import { apiClient } from "@scm-manager/ui-components"; import type { Action, Branch, Repository } from "@scm-manager/ui-types"; @@ -104,7 +104,9 @@ export function createBranch( export function getBranches(state: Object, repository: Repository) { const repoState = getRepoState(state, repository); if (repoState && repoState.list) { - return repoState.list._embedded.branches.map(name => repoState.byName[name]); + return repoState.list._embedded.branches.map( + name => repoState.byName[name] + ); } } @@ -116,10 +118,17 @@ function getRepoState(state: Object, repository: Repository) { } } -export const isPermittedToCreateBranches = (state: Object, repository: Repository): boolean => { +export const isPermittedToCreateBranches = ( + state: Object, + repository: Repository +): boolean => { const repoState = getRepoState(state, repository); - return !!(repoState && repoState.list && repoState.list._links && repoState.list._links.create); - + return !!( + repoState && + repoState.list && + repoState.list._links && + repoState.list._links.create + ); }; export function getBranch( diff --git a/scm-ui/src/repos/branches/modules/branches.test.js b/scm-ui/src/repos/branches/modules/branches.test.js index 3f81d8bb5b..ef28a981c8 100644 --- a/scm-ui/src/repos/branches/modules/branches.test.js +++ b/scm-ui/src/repos/branches/modules/branches.test.js @@ -22,7 +22,8 @@ import reducer, { isFetchBranchesPending, createBranch, isCreateBranchPending, - getCreateBranchFailure, isPermittedToCreateBranches + getCreateBranchFailure, + isPermittedToCreateBranches } from "./branches"; const namespace = "foo"; @@ -142,11 +143,13 @@ describe("branches", () => { fetchMock.getOnce(URL + "/newBranch", newBranch); const store = mockStore({}); - return store.dispatch(createBranch(URL, repository, branchRequest)).then(() => { - const actions = store.getActions(); - expect(actions[0].type).toEqual(CREATE_BRANCH_PENDING); - expect(actions[1].type).toEqual(CREATE_BRANCH_SUCCESS); - }); + return store + .dispatch(createBranch(URL, repository, branchRequest)) + .then(() => { + const actions = store.getActions(); + expect(actions[0].type).toEqual(CREATE_BRANCH_PENDING); + expect(actions[1].type).toEqual(CREATE_BRANCH_SUCCESS); + }); }); it("should call the callback with the branch from the location header", () => { @@ -165,13 +168,15 @@ describe("branches", () => { let receivedBranch = null; - const callback = (branch) => { + const callback = branch => { receivedBranch = branch; }; - return store.dispatch(createBranch(URL, repository, branchRequest, callback)).then(() => { - expect(receivedBranch).toEqual(newBranch); - }); + return store + .dispatch(createBranch(URL, repository, branchRequest, callback)) + .then(() => { + expect(receivedBranch).toEqual(newBranch); + }); }); it("should fail creating a branch on HTTP 500", () => { @@ -180,11 +185,13 @@ describe("branches", () => { }); const store = mockStore({}); - return store.dispatch(createBranch(URL, repository, branchRequest)).then(() => { - const actions = store.getActions(); - expect(actions[0].type).toEqual(CREATE_BRANCH_PENDING); - expect(actions[1].type).toEqual(CREATE_BRANCH_FAILURE); - }); + return store + .dispatch(createBranch(URL, repository, branchRequest)) + .then(() => { + const actions = store.getActions(); + expect(actions[0].type).toEqual(CREATE_BRANCH_PENDING); + expect(actions[1].type).toEqual(CREATE_BRANCH_FAILURE); + }); }); }); @@ -215,7 +222,7 @@ describe("branches", () => { const repoState = newState["foo/bar"]; expect(repoState.list._links.create.href).toEqual("/create"); - expect(repoState.list._embedded.branches).toEqual([ "branch1", "branch2" ]); + expect(repoState.list._embedded.branches).toEqual(["branch1", "branch2"]); expect(repoState.byName.branch1).toEqual(branch1); expect(repoState.byName.branch2).toEqual(branch2); @@ -233,15 +240,13 @@ describe("branches", () => { const state = { "foo/bar": { list: { - _links: { - - }, + _links: {}, _embedded: { branches: ["branch1"] } }, byName: { - "branch1": branch1 + branch1: branch1 } } }; @@ -258,7 +263,7 @@ describe("branches", () => { const state = { "scm/core": { byName: { - "branch1": branch1 + branch1: branch1 } } }; @@ -272,9 +277,9 @@ describe("branches", () => { const state = { "foo/bar": { byName: { - "branch1": { - "name": "branch1", - "revision": "xyz" + branch1: { + name: "branch1", + revision: "xyz" } } } @@ -289,6 +294,8 @@ describe("branches", () => { const state = { "foo/bar": { byName: { + branch1, + branch2, branch3 } } @@ -299,7 +306,6 @@ describe("branches", () => { expect(newState["foo/bar"].byName.branch2).toEqual(branch2); expect(newState["foo/bar"].byName.branch3).toEqual(branch3); }); - }); describe("branch selectors", () => { @@ -309,16 +315,14 @@ describe("branches", () => { branches: { "foo/bar": { list: { - _links: { - - }, + _links: {}, _embedded: { branches: ["branch1", "branch2"] } }, byName: { - "branch1": branch1, - "branch2": branch2 + branch1: branch1, + branch2: branch2 } } } @@ -344,7 +348,7 @@ describe("branches", () => { it("should return always the same reference for branches", () => { const one = getBranches(state, repository); const two = getBranches(state, repository); - expect(one).toBe(two); + expect(one).toEqual(two); }); it("should return undefined, if no branches for the repository available", () => { @@ -380,7 +384,7 @@ describe("branches", () => { href: "http://create-it" } } - }, + } } } }; From a4ee29d8e5d87529835552e8cd14ab45e7f338cc Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Tue, 9 Apr 2019 14:36:29 +0200 Subject: [PATCH 52/79] added no groups found notification --- scm-ui/public/locales/de/groups.json | 3 ++- scm-ui/public/locales/en/groups.json | 3 ++- scm-ui/src/groups/containers/Groups.js | 27 +++++++++++++++++--------- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/scm-ui/public/locales/de/groups.json b/scm-ui/public/locales/de/groups.json index 897cab02d3..bb8eda1cf2 100644 --- a/scm-ui/public/locales/de/groups.json +++ b/scm-ui/public/locales/de/groups.json @@ -10,7 +10,8 @@ }, "groups": { "title": "Gruppen", - "subtitle": "Verwaltung der Gruppen" + "subtitle": "Verwaltung der Gruppen", + "noGroups": "Keine Gruppen gefunden." }, "singleGroup": { "errorTitle": "Fehler", diff --git a/scm-ui/public/locales/en/groups.json b/scm-ui/public/locales/en/groups.json index a54249988a..aff350a458 100644 --- a/scm-ui/public/locales/en/groups.json +++ b/scm-ui/public/locales/en/groups.json @@ -10,7 +10,8 @@ }, "groups": { "title": "Groups", - "subtitle": "Create, read, update and delete groups" + "subtitle": "Create, read, update and delete groups", + "noGroups": "No groups found." }, "singleGroup": { "errorTitle": "Error", diff --git a/scm-ui/src/groups/containers/Groups.js b/scm-ui/src/groups/containers/Groups.js index 78e373e261..8315614b4c 100644 --- a/scm-ui/src/groups/containers/Groups.js +++ b/scm-ui/src/groups/containers/Groups.js @@ -24,6 +24,7 @@ import { selectListAsCollection } from "../modules/groups"; import { getGroupsLink } from "../../modules/indexResource"; +import Notification from "@scm-manager/ui-components/src/Notification"; type Props = { groups: Group[], @@ -75,14 +76,26 @@ class Groups extends React.Component { loading={loading || !groups} error={error} > - - {this.renderPaginator()} + {this.renderGroupTable()} {this.renderCreateButton()} {this.renderPageActionCreateButton()} ); } + renderGroupTable() { + const { groups, t } = this.props; + if (groups && groups.length > 0) { + return ( + <> + + {this.renderPaginator()} + + ); + } + return {t("groups.noGroups")}; + } + renderPaginator() { const { list } = this.props; if (list) { @@ -93,12 +106,9 @@ class Groups extends React.Component { renderCreateButton() { if (this.props.canAddGroups) { - return ( - - ); - } else { - return; + return ; } + return null; } renderPageActionCreateButton() { @@ -112,9 +122,8 @@ class Groups extends React.Component { /> ); - } else { - return; } + return null; } } From 24da17e0a917ba0cffe7233c27b3de07dad0678d Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Tue, 9 Apr 2019 14:47:37 +0200 Subject: [PATCH 53/79] added no repositories found notification --- scm-ui/public/locales/de/repos.json | 1 + scm-ui/public/locales/en/repos.json | 1 + scm-ui/src/groups/containers/Groups.js | 5 ++--- scm-ui/src/repos/containers/Overview.js | 26 ++++++++++++++++++++----- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/scm-ui/public/locales/de/repos.json b/scm-ui/public/locales/de/repos.json index 6f7846df72..38adddf7dd 100644 --- a/scm-ui/public/locales/de/repos.json +++ b/scm-ui/public/locales/de/repos.json @@ -37,6 +37,7 @@ "overview": { "title": "Repositories", "subtitle": "Übersicht aller verfügbaren Repositories", + "noRepositories": "Keine Repositories gefunden.", "createButton": "Repository erstellen" }, "create": { diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 89492f427d..84f7fd5740 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -37,6 +37,7 @@ "overview": { "title": "Repositories", "subtitle": "Overview of available repositories", + "noRepositories": "No repositories found.", "createButton": "Create Repository" }, "create": { diff --git a/scm-ui/src/groups/containers/Groups.js b/scm-ui/src/groups/containers/Groups.js index 8315614b4c..d54f4f5be4 100644 --- a/scm-ui/src/groups/containers/Groups.js +++ b/scm-ui/src/groups/containers/Groups.js @@ -2,13 +2,13 @@ import React from "react"; import { connect } from "react-redux"; import { translate } from "react-i18next"; -import type { Group } from "@scm-manager/ui-types"; -import type { PagedCollection } from "@scm-manager/ui-types"; +import type { Group, PagedCollection } from "@scm-manager/ui-types"; import type { History } from "history"; import { Page, PageActions, Button, + Notification, Paginator } from "@scm-manager/ui-components"; import { GroupTable } from "./../components/table"; @@ -24,7 +24,6 @@ import { selectListAsCollection } from "../modules/groups"; import { getGroupsLink } from "../../modules/indexResource"; -import Notification from "@scm-manager/ui-components/src/Notification"; type Props = { groups: Group[], diff --git a/scm-ui/src/repos/containers/Overview.js b/scm-ui/src/repos/containers/Overview.js index 1d672aafb1..b8cffae191 100644 --- a/scm-ui/src/repos/containers/Overview.js +++ b/scm-ui/src/repos/containers/Overview.js @@ -19,6 +19,7 @@ import { PageActions, Button, CreateButton, + Notification, Paginator } from "@scm-manager/ui-components"; import RepositoryList from "../components/list"; @@ -72,19 +73,34 @@ class Overview extends React.Component { loading={loading} error={error} > - {this.renderList()} + {this.renderOverview()} {this.renderPageActionCreateButton()} ); } - renderList() { - const { collection, fetchReposByLink } = this.props; + renderRepositoryList() { + const { collection, fetchReposByLink, t } = this.props; + + if (collection._embedded && collection._embedded.repositories.length > 0) { + return ( + <> + + + + ); + } + return ( + {t("overview.noRepositories")} + ); + } + + renderOverview() { + const { collection } = this.props; if (collection) { return (
- - + {this.renderRepositoryList()} {this.renderCreateButton()}
); From a05fa8d46f3d377c07c5b7aace7ed9e3e6fabee8 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Tue, 9 Apr 2019 14:59:19 +0200 Subject: [PATCH 54/79] added no users found notification --- scm-ui/public/locales/de/users.json | 1 + scm-ui/public/locales/en/users.json | 1 + scm-ui/src/users/containers/Users.js | 19 ++++++++++++++++--- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/scm-ui/public/locales/de/users.json b/scm-ui/public/locales/de/users.json index 96d632e55d..05b5cf142f 100644 --- a/scm-ui/public/locales/de/users.json +++ b/scm-ui/public/locales/de/users.json @@ -24,6 +24,7 @@ "users": { "title": "Benutzer", "subtitle": "Verwaltung der Benutzer", + "noUsers": "Keine Benutzer gefunden.", "createButton": "Benutzer erstellen" }, "singleUser": { diff --git a/scm-ui/public/locales/en/users.json b/scm-ui/public/locales/en/users.json index 91e4efe87f..e6fd822ead 100644 --- a/scm-ui/public/locales/en/users.json +++ b/scm-ui/public/locales/en/users.json @@ -24,6 +24,7 @@ "users": { "title": "Users", "subtitle": "Create, read, update and delete users", + "noUsers": "No users found.", "createButton": "Create User" }, "singleUser": { diff --git a/scm-ui/src/users/containers/Users.js b/scm-ui/src/users/containers/Users.js index 3d88523479..66a4102fa8 100644 --- a/scm-ui/src/users/containers/Users.js +++ b/scm-ui/src/users/containers/Users.js @@ -19,7 +19,8 @@ import { PageActions, Button, CreateButton, - Paginator + Paginator, + Notification } from "@scm-manager/ui-components"; import { UserTable } from "./../components/table"; import type { User, PagedCollection } from "@scm-manager/ui-types"; @@ -75,14 +76,26 @@ class Users extends React.Component { loading={loading || !users} error={error} > - - {this.renderPaginator()} + {this.renderUserTable()} {this.renderCreateButton()} {this.renderPageActionCreateButton()} ); } + renderUserTable() { + const { users, t } = this.props; + if (users && users.length > 0) { + return ( + <> + + {this.renderPaginator()} + + ); + } + return {t("users.noUsers")}; + } + renderPaginator() { const { list } = this.props; if (list) { From df7c915363eb35f16a4aaa94f311270db559e63a Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Tue, 9 Apr 2019 15:06:55 +0200 Subject: [PATCH 55/79] added no branches found notification --- scm-ui/public/locales/de/repos.json | 1 + scm-ui/public/locales/en/repos.json | 1 + .../branches/containers/BranchesOverview.js | 16 ++++++++++++---- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/scm-ui/public/locales/de/repos.json b/scm-ui/public/locales/de/repos.json index 38adddf7dd..9979ebf21f 100644 --- a/scm-ui/public/locales/de/repos.json +++ b/scm-ui/public/locales/de/repos.json @@ -47,6 +47,7 @@ "branches": { "overview": { "title": "Übersicht aller verfügbaren Branches", + "noBranches": "Keine Branches gefunden.", "createButton": "Branch erstellen" }, "table": { diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 84f7fd5740..eb950b221f 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -47,6 +47,7 @@ "branches": { "overview": { "title": "Overview of all branches", + "noBranches": "No branches found.", "createButton": "Create Branch" }, "table": { diff --git a/scm-ui/src/repos/branches/containers/BranchesOverview.js b/scm-ui/src/repos/branches/containers/BranchesOverview.js index f8c8be7529..d792c0828a 100644 --- a/scm-ui/src/repos/branches/containers/BranchesOverview.js +++ b/scm-ui/src/repos/branches/containers/BranchesOverview.js @@ -16,6 +16,7 @@ import { CreateButton, ErrorNotification, Loading, + Notification, Subtitle } from "@scm-manager/ui-components"; import BranchTable from "../components/BranchTable"; @@ -44,7 +45,7 @@ class BranchesOverview extends React.Component { } render() { - const { baseUrl, loading, error, branches, t } = this.props; + const { loading, error, branches, t } = this.props; if (error) { return ; @@ -54,17 +55,24 @@ class BranchesOverview extends React.Component { return ; } - orderBranches(branches); - return ( <> - + {this.renderBranchesTable()} {this.renderCreateButton()} ); } + renderBranchesTable() { + const { baseUrl, branches, t } = this.props; + if (branches && branches.length > 0) { + orderBranches(branches); + return ; + } + return {t("branches.overview.noBranches")}; + } + renderCreateButton() { const { showCreateButton, t } = this.props; if (showCreateButton || true) { From 818076ac6f270e1befd2b82892a82935286e9d7c Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Tue, 9 Apr 2019 15:19:43 +0200 Subject: [PATCH 56/79] added no changesets found notification --- scm-ui/public/locales/de/repos.json | 1 + scm-ui/public/locales/en/repos.json | 1 + scm-ui/src/repos/containers/Changesets.js | 18 ++++++++++++++---- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/scm-ui/public/locales/de/repos.json b/scm-ui/public/locales/de/repos.json index 9979ebf21f..0a42e49b6f 100644 --- a/scm-ui/public/locales/de/repos.json +++ b/scm-ui/public/locales/de/repos.json @@ -69,6 +69,7 @@ "changesets": { "errorTitle": "Fehler", "errorSubtitle": "Changesets konnten nicht abgerufen werden", + "noChangesets": "Keine Changesets gefunden.", "branchSelectorLabel": "Branches" }, "changeset": { diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index eb950b221f..8b245cb2a6 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -69,6 +69,7 @@ "changesets": { "errorTitle": "Error", "errorSubtitle": "Could not fetch changesets", + "noChangesets": "No changesets found.", "branchSelectorLabel": "Branches" }, "changeset": { diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index 5edc677e60..ce15834f31 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -22,9 +22,11 @@ import { getPageFromMatch, LinkPaginator, ChangesetList, - Loading + Loading, + Notification } from "@scm-manager/ui-components"; import { compose } from "redux"; +import { translate } from "react-i18next"; type Props = { repository: Repository, @@ -41,7 +43,8 @@ type Props = { fetchChangesets: (Repository, Branch, number) => void, // context props - match: any + match: any, + t: string => string }; class Changesets extends React.Component { @@ -52,7 +55,7 @@ class Changesets extends React.Component { } render() { - const { changesets, loading, error } = this.props; + const { changesets, loading, error, t } = this.props; if (error) { return ; @@ -63,7 +66,13 @@ class Changesets extends React.Component { } if (!changesets || changesets.length === 0) { - return null; + return ( +
+ + {t("changesets.noChangesets")} + +
+ ); } return ( <> @@ -115,6 +124,7 @@ const mapStateToProps = (state: any, ownProps: Props) => { }; export default compose( + translate("repos"), withRouter, connect( mapStateToProps, From 4a6fd9721fc136b5cd193e8882d6534f786a9f02 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Tue, 9 Apr 2019 15:31:12 +0200 Subject: [PATCH 57/79] added no sources found notification --- scm-ui/public/locales/de/repos.json | 3 +- scm-ui/public/locales/en/repos.json | 3 +- .../src/repos/sources/components/FileTree.js | 86 ++++++++++--------- .../src/repos/sources/containers/Sources.js | 4 +- 4 files changed, 49 insertions(+), 47 deletions(-) diff --git a/scm-ui/public/locales/de/repos.json b/scm-ui/public/locales/de/repos.json index 0a42e49b6f..4093d1c2e0 100644 --- a/scm-ui/public/locales/de/repos.json +++ b/scm-ui/public/locales/de/repos.json @@ -108,7 +108,8 @@ "lastModified": "Zuletzt bearbeitet", "description": "Beschreibung", "size": "Größe" - } + }, + "noSources": "Keine Sources gefunden." }, "permission": { "title": "Berechtigungen bearbeiten", diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 8b245cb2a6..44d2e09468 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -108,7 +108,8 @@ "lastModified": "Last modified", "description": "Description", "size": "Size" - } + }, + "noSources": "No sources found." }, "permission": { "title": "Edit Permissions", diff --git a/scm-ui/src/repos/sources/components/FileTree.js b/scm-ui/src/repos/sources/components/FileTree.js index 18ef1b01c5..960f6b42d6 100644 --- a/scm-ui/src/repos/sources/components/FileTree.js +++ b/scm-ui/src/repos/sources/components/FileTree.js @@ -5,7 +5,11 @@ import { connect } from "react-redux"; import injectSheet from "react-jss"; import FileTreeLeaf from "./FileTreeLeaf"; import type { Repository, File } from "@scm-manager/ui-types"; -import { ErrorNotification, Loading } from "@scm-manager/ui-components"; +import { + ErrorNotification, + Loading, + Notification +} from "@scm-manager/ui-components"; import { getFetchSourcesFailure, isFetchSourcesPending, @@ -48,16 +52,34 @@ export function findParent(path: string) { class FileTree extends React.Component { render() { - const { - error, - loading, - tree, - revision, - path, - baseUrl, - classes, - t - } = this.props; + const { error, loading, tree } = this.props; + + if (error) { + return ; + } + + if (loading) { + return ; + } + if (!tree) { + return null; + } + + return
{this.renderSourcesTable()}
; + } + + renderSourcesTable() { + const { tree, revision, path, baseUrl, classes, t } = this.props; + + const files = []; + + if (path) { + files.push({ + name: "..", + path: findParent(path), + directory: true + }); + } const compareFiles = function(f1: File, f2: File): number { if (f1.directory) { @@ -75,40 +97,19 @@ class FileTree extends React.Component { } }; - if (error) { - return ; - } - - if (loading) { - return ; - } - if (!tree) { - return null; - } - - const files = []; - - if (path) { - files.push({ - name: "..", - path: findParent(path), - directory: true - }); - } - if (tree._embedded && tree._embedded.children) { files.push(...tree._embedded.children.sort(compareFiles)); } - let baseUrlWithRevision = baseUrl; - if (revision) { - baseUrlWithRevision += "/" + encodeURIComponent(revision); - } else { - baseUrlWithRevision += "/" + encodeURIComponent(tree.revision); - } + if (files && files.length > 0) { + let baseUrlWithRevision = baseUrl; + if (revision) { + baseUrlWithRevision += "/" + encodeURIComponent(revision); + } else { + baseUrlWithRevision += "/" + encodeURIComponent(tree.revision); + } - return ( -
+ return ( @@ -135,8 +136,9 @@ class FileTree extends React.Component { ))}
-
- ); + ); + } + return {t("sources.noSources")}; } } diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index 7b87776e73..2d7e1ca468 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -94,9 +94,7 @@ class Sources extends React.Component { if (currentFileIsDirectory) { return (
-
- {this.renderBranchSelector()} -
+
{this.renderBranchSelector()}
Date: Tue, 9 Apr 2019 15:33:47 +0200 Subject: [PATCH 58/79] specificated no sources/changesets found notification considering branches --- scm-ui/public/locales/de/repos.json | 4 ++-- scm-ui/public/locales/en/repos.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scm-ui/public/locales/de/repos.json b/scm-ui/public/locales/de/repos.json index 4093d1c2e0..47353fa79b 100644 --- a/scm-ui/public/locales/de/repos.json +++ b/scm-ui/public/locales/de/repos.json @@ -69,7 +69,7 @@ "changesets": { "errorTitle": "Fehler", "errorSubtitle": "Changesets konnten nicht abgerufen werden", - "noChangesets": "Keine Changesets gefunden.", + "noChangesets": "Keine Changesets in diesem Branch gefunden.", "branchSelectorLabel": "Branches" }, "changeset": { @@ -109,7 +109,7 @@ "description": "Beschreibung", "size": "Größe" }, - "noSources": "Keine Sources gefunden." + "noSources": "Keine Sources in diesem Branch gefunden." }, "permission": { "title": "Berechtigungen bearbeiten", diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 44d2e09468..45452ae257 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -69,7 +69,7 @@ "changesets": { "errorTitle": "Error", "errorSubtitle": "Could not fetch changesets", - "noChangesets": "No changesets found.", + "noChangesets": "No changesets matching this branch found.", "branchSelectorLabel": "Branches" }, "changeset": { @@ -109,7 +109,7 @@ "description": "Description", "size": "Size" }, - "noSources": "No sources found." + "noSources": "No sources matching this branch found." }, "permission": { "title": "Edit Permissions", From 872a2572de18b35e68d3c340e1b7a8ab43c76f8b Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Wed, 10 Apr 2019 08:18:38 +0000 Subject: [PATCH 59/79] Close branch bugfix/repo_horizontal_lines From 49775bc6316bb92f97f7ed6ed59552f5dafd11eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 10 Apr 2019 10:46:49 +0200 Subject: [PATCH 60/79] Change texts --- scm-ui/public/locales/en/repos.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 45452ae257..0d59b88819 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -69,7 +69,7 @@ "changesets": { "errorTitle": "Error", "errorSubtitle": "Could not fetch changesets", - "noChangesets": "No changesets matching this branch found.", + "noChangesets": "No changesets found for this branch.", "branchSelectorLabel": "Branches" }, "changeset": { @@ -109,7 +109,7 @@ "description": "Description", "size": "Size" }, - "noSources": "No sources matching this branch found." + "noSources": "No sources found for this branch." }, "permission": { "title": "Edit Permissions", From 863ca0afca2b565e991ac68cc9b76fc362d3d74f Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Wed, 10 Apr 2019 08:47:44 +0000 Subject: [PATCH 61/79] Close branch bugfix/notification_for_empty_table From d3b1a1b7aaf42232b5f7202e94c637e50cf7350f Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Wed, 10 Apr 2019 08:50:36 +0000 Subject: [PATCH 62/79] Close branch bugfix/mailto_emptyspace From 05ccf513cbe3bfd5b34774b9984048b766092ad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 10 Apr 2019 13:23:54 +0200 Subject: [PATCH 63/79] Initialize state for branches --- scm-ui/src/repos/branches/modules/branches.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-ui/src/repos/branches/modules/branches.js b/scm-ui/src/repos/branches/modules/branches.js index 3979b210d9..c6cb7a8215 100644 --- a/scm-ui/src/repos/branches/modules/branches.js +++ b/scm-ui/src/repos/branches/modules/branches.js @@ -311,7 +311,7 @@ const reduceByBranchSuccess = (state, payload) => { }; export default function reducer( - state: {}, + state: {} = {}, action: Action = { type: "UNKNOWN" } ): Object { if (!action.payload) { From 260770d7c52841f3ef52bc1a8bf63be9a4f4f990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 10 Apr 2019 15:33:50 +0200 Subject: [PATCH 64/79] Fix create branches --- .../packages/ui-types/src/Branches.js | 5 +++ .../packages/ui-types/src/index.js | 2 +- .../repos/branches/components/BranchForm.js | 9 ++-- .../repos/branches/containers/CreateBranch.js | 41 ++++++++++++++----- scm-ui/src/repos/branches/modules/branches.js | 23 ++++++++--- 5 files changed, 60 insertions(+), 20 deletions(-) diff --git a/scm-ui-components/packages/ui-types/src/Branches.js b/scm-ui-components/packages/ui-types/src/Branches.js index 2dee365424..6d501af593 100644 --- a/scm-ui-components/packages/ui-types/src/Branches.js +++ b/scm-ui-components/packages/ui-types/src/Branches.js @@ -7,3 +7,8 @@ export type Branch = { defaultBranch?: boolean, _links: Links } + +export type BranchRequest = { + name: string, + parent: string +} diff --git a/scm-ui-components/packages/ui-types/src/index.js b/scm-ui-components/packages/ui-types/src/index.js index 02e88f12e1..c57a3c6792 100644 --- a/scm-ui-components/packages/ui-types/src/index.js +++ b/scm-ui-components/packages/ui-types/src/index.js @@ -9,7 +9,7 @@ export type { Group, Member } from "./Group"; export type { Repository, RepositoryCollection, RepositoryGroup } from "./Repositories"; export type { RepositoryType, RepositoryTypeCollection } from "./RepositoryTypes"; -export type { Branch } from "./Branches"; +export type { Branch, BranchRequest } from "./Branches"; export type { Changeset } from "./Changesets"; diff --git a/scm-ui/src/repos/branches/components/BranchForm.js b/scm-ui/src/repos/branches/components/BranchForm.js index 2938b06a2a..d72e2196ba 100644 --- a/scm-ui/src/repos/branches/components/BranchForm.js +++ b/scm-ui/src/repos/branches/components/BranchForm.js @@ -1,7 +1,7 @@ // @flow import React from "react"; import { translate } from "react-i18next"; -import type { Repository, Branch } from "@scm-manager/ui-types"; +import type { Repository, Branch, BranchRequest } from "@scm-manager/ui-types"; import { Select, InputField, @@ -11,7 +11,7 @@ import { import { orderBranches } from "../util/orderBranches"; type Props = { - submitForm: Branch => void, + submitForm: BranchRequest => void, repository: Repository, branches: Branch[], loading?: boolean, @@ -51,7 +51,10 @@ class BranchForm extends React.Component { submit = (event: Event) => { event.preventDefault(); if (this.isValid()) { - this.props.submitForm(this.state.branch); + this.props.submitForm({ + name: this.state.name, + parent: this.state.source + }); } }; diff --git a/scm-ui/src/repos/branches/containers/CreateBranch.js b/scm-ui/src/repos/branches/containers/CreateBranch.js index 1b0ef76408..a38d483af7 100644 --- a/scm-ui/src/repos/branches/containers/CreateBranch.js +++ b/scm-ui/src/repos/branches/containers/CreateBranch.js @@ -7,10 +7,11 @@ import { } from "@scm-manager/ui-components"; import { translate } from "react-i18next"; import BranchForm from "../components/BranchForm"; -import type { Repository, Branch } from "@scm-manager/ui-types"; +import type { Repository, Branch, BranchRequest } from "@scm-manager/ui-types"; import { fetchBranches, getBranches, + getBrancheCreateLink, createBranch, createBranchReset, isCreateBranchPending, @@ -28,10 +29,16 @@ type Props = { error?: Error, repository: Repository, branches: Branch[], + createBranchesLink: string, // dispatcher functions fetchBranches: Repository => void, - createBranch: (branch: Branch, callback?: () => void) => void, + createBranch: ( + createLink: string, + repository: Repository, + branch: BranchRequest, + callback?: (Branch) => void + ) => void, resetForm: () => void, // context objects @@ -48,12 +55,21 @@ class CreateBranch extends React.Component { } branchCreated = (branch: Branch) => { - const { history } = this.props; - history.push("/branch/" + encodeURIComponent(branch.name) + "/info"); + const { history, repository } = this.props; + history.push( + `/repo/${repository.namespace}/${ + repository.name + }/branch/${encodeURIComponent(branch.name)}/info` + ); }; - createBranch = (branch: Branch) => { - this.props.createBranch(branch, () => this.branchCreated(branch)); + createBranch = (branch: BranchRequest) => { + this.props.createBranch( + this.props.createBranchesLink, + this.props.repository, + branch, + newBranch => this.branchCreated(newBranch) + ); }; transmittedName = (url: string) => { @@ -76,7 +92,7 @@ class CreateBranch extends React.Component { <> this.createBranch(branch)} + submitForm={branchRequest => this.createBranch(branchRequest)} loading={loading} repository={repository} branches={branches} @@ -93,11 +109,12 @@ const mapDispatchToProps = dispatch => { dispatch(fetchBranches(repository)); }, createBranch: ( + createLink: string, repository: Repository, - branch: Branch, - callback?: () => void + branchRequest: BranchRequest, + callback?: (newBranch: Branch) => void ) => { - dispatch(createBranch("INSERTLINK", repository, branch, callback)); //TODO + dispatch(createBranch(createLink, repository, branchRequest, callback)); }, resetForm: () => { dispatch(createBranchReset()); @@ -111,11 +128,13 @@ const mapStateToProps = (state, ownProps) => { const error = getFetchBranchesFailure(state, repository) || getCreateBranchFailure(state); const branches = getBranches(state, repository); + const createBranchesLink = getBrancheCreateLink(state, repository); return { repository, loading, error, - branches + branches, + createBranchesLink }; }; diff --git a/scm-ui/src/repos/branches/modules/branches.js b/scm-ui/src/repos/branches/modules/branches.js index c6cb7a8215..5fb3da5994 100644 --- a/scm-ui/src/repos/branches/modules/branches.js +++ b/scm-ui/src/repos/branches/modules/branches.js @@ -5,7 +5,12 @@ import { SUCCESS_SUFFIX } from "../../../modules/types"; import { apiClient } from "@scm-manager/ui-components"; -import type { Action, Branch, Repository } from "@scm-manager/ui-types"; +import type { + Action, + Branch, + BranchRequest, + Repository +} from "@scm-manager/ui-types"; import { isPending } from "../../../modules/pending"; import { getFailure } from "../../../modules/failure"; @@ -25,7 +30,8 @@ export const CREATE_BRANCH_SUCCESS = `${CREATE_BRANCH}_${PENDING_SUFFIX}`; export const CREATE_BRANCH_FAILURE = `${CREATE_BRANCH}_${PENDING_SUFFIX}`; export const CREATE_BRANCH_RESET = `${CREATE_BRANCH}_${PENDING_SUFFIX}`; -const CONTENT_TYPE_BRANCH = "application/vnd.scmm-branch+json;v=2"; +const CONTENT_TYPE_BRANCH_REQUEST = + "application/vnd.scmm-branchRequest+json;v=2"; // Fetching branches @@ -79,13 +85,13 @@ export function fetchBranch(repository: Repository, name: string) { export function createBranch( link: string, repository: Repository, - branch: Branch, + branchRequest: BranchRequest, callback?: (branch: Branch) => void ) { return function(dispatch: any) { - dispatch(createBranchPending(repository, branch.name)); + dispatch(createBranchPending(repository, branchRequest.name)); return apiClient - .post(link, branch, CONTENT_TYPE_BRANCH) + .post(link, branchRequest, CONTENT_TYPE_BRANCH_REQUEST) .then(response => response.headers.get("Location")) .then(location => apiClient.get(location)) .then(response => response.json()) @@ -110,6 +116,13 @@ export function getBranches(state: Object, repository: Repository) { } } +export function getBrancheCreateLink(state: Object, repository: Repository) { + const repoState = getRepoState(state, repository); + if (repoState && repoState.list) { + return repoState.list._links.create.href; + } +} + function getRepoState(state: Object, repository: Repository) { const key = createKey(repository); const repoState = state.branches[key]; From 2ada10ab8d8fdee974a540a4577d94f5a4cf8e8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 10 Apr 2019 16:20:55 +0200 Subject: [PATCH 65/79] Fix pending states --- .../repos/branches/containers/CreateBranch.js | 16 +++--- scm-ui/src/repos/branches/modules/branches.js | 50 +++++++++++-------- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/scm-ui/src/repos/branches/containers/CreateBranch.js b/scm-ui/src/repos/branches/containers/CreateBranch.js index a38d483af7..3bfc4ac651 100644 --- a/scm-ui/src/repos/branches/containers/CreateBranch.js +++ b/scm-ui/src/repos/branches/containers/CreateBranch.js @@ -11,7 +11,7 @@ import type { Repository, Branch, BranchRequest } from "@scm-manager/ui-types"; import { fetchBranches, getBranches, - getBrancheCreateLink, + getBranchCreateLink, createBranch, createBranchReset, isCreateBranchPending, @@ -39,7 +39,7 @@ type Props = { branch: BranchRequest, callback?: (Branch) => void ) => void, - resetForm: () => void, + resetForm: Repository => void, // context objects t: string => string, @@ -51,7 +51,7 @@ class CreateBranch extends React.Component { componentDidMount() { const { fetchBranches, repository } = this.props; fetchBranches(repository); - this.props.resetForm(); + this.props.resetForm(repository); } branchCreated = (branch: Branch) => { @@ -116,19 +116,21 @@ const mapDispatchToProps = dispatch => { ) => { dispatch(createBranch(createLink, repository, branchRequest, callback)); }, - resetForm: () => { - dispatch(createBranchReset()); + resetForm: (repository: Repository) => { + dispatch(createBranchReset(repository)); } }; }; const mapStateToProps = (state, ownProps) => { const { repository } = ownProps; - const loading = isFetchBranchesPending(state, repository); //|| isCreateBranchPending(state); + const loading = + isFetchBranchesPending(state, repository) || + isCreateBranchPending(state, repository); const error = getFetchBranchesFailure(state, repository) || getCreateBranchFailure(state); const branches = getBranches(state, repository); - const createBranchesLink = getBrancheCreateLink(state, repository); + const createBranchesLink = getBranchCreateLink(state, repository); return { repository, loading, diff --git a/scm-ui/src/repos/branches/modules/branches.js b/scm-ui/src/repos/branches/modules/branches.js index 5fb3da5994..660a3e007c 100644 --- a/scm-ui/src/repos/branches/modules/branches.js +++ b/scm-ui/src/repos/branches/modules/branches.js @@ -2,7 +2,8 @@ import { FAILURE_SUFFIX, PENDING_SUFFIX, - SUCCESS_SUFFIX + SUCCESS_SUFFIX, + RESET_SUFFIX } from "../../../modules/types"; import { apiClient } from "@scm-manager/ui-components"; import type { @@ -26,9 +27,9 @@ export const FETCH_BRANCH_FAILURE = `${FETCH_BRANCH}_${FAILURE_SUFFIX}`; export const CREATE_BRANCH = "scm/repos/CREATE_BRANCH"; export const CREATE_BRANCH_PENDING = `${CREATE_BRANCH}_${PENDING_SUFFIX}`; -export const CREATE_BRANCH_SUCCESS = `${CREATE_BRANCH}_${PENDING_SUFFIX}`; -export const CREATE_BRANCH_FAILURE = `${CREATE_BRANCH}_${PENDING_SUFFIX}`; -export const CREATE_BRANCH_RESET = `${CREATE_BRANCH}_${PENDING_SUFFIX}`; +export const CREATE_BRANCH_SUCCESS = `${CREATE_BRANCH}_${SUCCESS_SUFFIX}`; +export const CREATE_BRANCH_FAILURE = `${CREATE_BRANCH}_${FAILURE_SUFFIX}`; +export const CREATE_BRANCH_RESET = `${CREATE_BRANCH}_${RESET_SUFFIX}`; const CONTENT_TYPE_BRANCH_REQUEST = "application/vnd.scmm-branchRequest+json;v=2"; @@ -89,19 +90,19 @@ export function createBranch( callback?: (branch: Branch) => void ) { return function(dispatch: any) { - dispatch(createBranchPending(repository, branchRequest.name)); + dispatch(createBranchPending(repository)); return apiClient .post(link, branchRequest, CONTENT_TYPE_BRANCH_REQUEST) .then(response => response.headers.get("Location")) .then(location => apiClient.get(location)) .then(response => response.json()) .then(branch => { - dispatch(createBranchSuccess()); + dispatch(createBranchSuccess(repository)); if (callback) { callback(branch); } }) - .catch(error => dispatch(createBranchFailure(error))); + .catch(error => dispatch(createBranchFailure(repository, error))); }; } @@ -116,7 +117,7 @@ export function getBranches(state: Object, repository: Repository) { } } -export function getBrancheCreateLink(state: Object, repository: Repository) { +export function getBranchCreateLink(state: Object, repository: Repository) { const repoState = getRepoState(state, repository); if (repoState && repoState.list) { return repoState.list._links.create.href; @@ -191,41 +192,46 @@ export function fetchBranchesFailure(repository: Repository, error: Error) { }; } -export function isCreateBranchPending(state: Object) { - return isPending(state, CREATE_BRANCH); +export function isCreateBranchPending(state: Object, repository: Repository) { + return isPending(state, CREATE_BRANCH, createKey(repository)); } export function getCreateBranchFailure(state: Object) { return getFailure(state, CREATE_BRANCH); } -export function createBranchPending( - repository: Repository, - name: string -): Action { +export function createBranchPending(repository: Repository): Action { return { type: CREATE_BRANCH_PENDING, - payload: { repository, name }, - itemId: createKey(repository) + "/" + name + payload: { repository }, + itemId: createKey(repository) }; } -export function createBranchSuccess(): Action { +export function createBranchSuccess(repository: Repository): Action { return { - type: CREATE_BRANCH_SUCCESS + type: CREATE_BRANCH_SUCCESS, + payload: { repository }, + itemId: createKey(repository) }; } -export function createBranchFailure(error: Error): Action { +export function createBranchFailure( + repository: Repository, + error: Error +): Action { return { type: CREATE_BRANCH_FAILURE, - payload: error + payload: { repository, error }, + itemId: createKey(repository) }; } -export function createBranchReset(): Action { +export function createBranchReset(repository: Repository): Action { return { - type: CREATE_BRANCH_RESET + type: CREATE_BRANCH_RESET, + payload: { repository }, + itemId: createKey(repository) }; } From 727fd08b3b612b826d01e0bfe867413c4eba368f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 10 Apr 2019 16:38:56 +0200 Subject: [PATCH 66/79] Disable create view when create link is missing --- .../packages/ui-components/src/forms/Select.js | 6 ++++-- scm-ui/src/repos/branches/components/BranchForm.js | 8 +++++--- .../src/repos/branches/containers/BranchesOverview.js | 10 ++++++---- scm-ui/src/repos/branches/containers/CreateBranch.js | 4 +++- scm-ui/src/repos/branches/modules/branches.js | 2 +- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/forms/Select.js b/scm-ui-components/packages/ui-components/src/forms/Select.js index 38e1cdab33..5d37e3d631 100644 --- a/scm-ui-components/packages/ui-components/src/forms/Select.js +++ b/scm-ui-components/packages/ui-components/src/forms/Select.js @@ -15,7 +15,8 @@ type Props = { value?: string, onChange: (value: string, name?: string) => void, loading?: boolean, - helpText?: string + helpText?: string, + disabled?: boolean }; class Select extends React.Component { @@ -34,7 +35,7 @@ class Select extends React.Component { }; render() { - const { options, value, label, helpText, loading } = this.props; + const { options, value, label, helpText, loading, disabled } = this.props; const loadingClass = loading ? "is-loading" : ""; @@ -51,6 +52,7 @@ class Select extends React.Component { }} value={value} onChange={this.handleInput} + disabled={disabled} > {options.map(opt => { return ( diff --git a/scm-ui/src/repos/branches/components/BranchForm.js b/scm-ui/src/repos/branches/components/BranchForm.js index d72e2196ba..1219511345 100644 --- a/scm-ui/src/repos/branches/components/BranchForm.js +++ b/scm-ui/src/repos/branches/components/BranchForm.js @@ -16,6 +16,7 @@ type Props = { branches: Branch[], loading?: boolean, transmittedName?: string, + disabled?: boolean, t: string => string }; @@ -59,7 +60,7 @@ class BranchForm extends React.Component { }; render() { - const { t, branches, loading, transmittedName } = this.props; + const { t, branches, loading, transmittedName, disabled } = this.props; const { name } = this.state; orderBranches(branches); const options = branches.map(branch => ({ @@ -78,6 +79,7 @@ class BranchForm extends React.Component { options={options} onChange={this.handleSourceChange} loading={loading} + disabled={disabled} /> { value={name ? name : ""} validationError={this.state.nameValidationError} errorMessage={t("validation.branch.nameInvalid")} - disabled={!!transmittedName} + disabled={!!transmittedName || disabled} />
diff --git a/scm-ui/src/repos/branches/containers/BranchesOverview.js b/scm-ui/src/repos/branches/containers/BranchesOverview.js index f8c8be7529..5f11f87116 100644 --- a/scm-ui/src/repos/branches/containers/BranchesOverview.js +++ b/scm-ui/src/repos/branches/containers/BranchesOverview.js @@ -4,7 +4,8 @@ import { fetchBranches, getBranches, getFetchBranchesFailure, - isFetchBranchesPending + isFetchBranchesPending, + isPermittedToCreateBranches } from "../modules/branches"; import { orderBranches } from "../util/orderBranches"; import { connect } from "react-redux"; @@ -67,8 +68,7 @@ class BranchesOverview extends React.Component { renderCreateButton() { const { showCreateButton, t } = this.props; - if (showCreateButton || true) { - // TODO + if (showCreateButton) { return ( { const loading = isFetchBranchesPending(state, repository); const error = getFetchBranchesFailure(state, repository); const branches = getBranches(state, repository); + const showCreateButton = isPermittedToCreateBranches(state, repository); return { repository, loading, error, - branches + branches, + showCreateButton }; }; diff --git a/scm-ui/src/repos/branches/containers/CreateBranch.js b/scm-ui/src/repos/branches/containers/CreateBranch.js index 3bfc4ac651..cefa712bad 100644 --- a/scm-ui/src/repos/branches/containers/CreateBranch.js +++ b/scm-ui/src/repos/branches/containers/CreateBranch.js @@ -30,6 +30,7 @@ type Props = { repository: Repository, branches: Branch[], createBranchesLink: string, + isPermittedToCreateBranches: boolean, // dispatcher functions fetchBranches: Repository => void, @@ -78,7 +79,7 @@ class CreateBranch extends React.Component { }; render() { - const { t, loading, error, repository, branches, location } = this.props; + const { t, loading, error, repository, branches, createBranchesLink, location } = this.props; if (error) { return ; @@ -97,6 +98,7 @@ class CreateBranch extends React.Component { repository={repository} branches={branches} transmittedName={this.transmittedName(location.search)} + disabled={!createBranchesLink} /> ); diff --git a/scm-ui/src/repos/branches/modules/branches.js b/scm-ui/src/repos/branches/modules/branches.js index 660a3e007c..054a303a05 100644 --- a/scm-ui/src/repos/branches/modules/branches.js +++ b/scm-ui/src/repos/branches/modules/branches.js @@ -119,7 +119,7 @@ export function getBranches(state: Object, repository: Repository) { export function getBranchCreateLink(state: Object, repository: Repository) { const repoState = getRepoState(state, repository); - if (repoState && repoState.list) { + if (repoState && repoState.list && repoState.list._links && repoState.list._links.create) { return repoState.list._links.create.href; } } From 3bfd9becc97b9800c7f2ae611e01e92542496021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 10 Apr 2019 17:34:31 +0200 Subject: [PATCH 67/79] Add button to switch between side-by-side and combined diff view --- .../ui-components/src/repos/DiffFile.js | 42 ++++++++++++------- scm-ui/public/locales/de/repos.json | 4 ++ scm-ui/public/locales/en/repos.json | 4 +- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/repos/DiffFile.js b/scm-ui-components/packages/ui-components/src/repos/DiffFile.js index ccc07f2ea1..64450659f0 100644 --- a/scm-ui-components/packages/ui-components/src/repos/DiffFile.js +++ b/scm-ui-components/packages/ui-components/src/repos/DiffFile.js @@ -11,6 +11,7 @@ import { import injectSheets from "react-jss"; import classNames from "classnames"; import { translate } from "react-i18next"; +import {Button} from "../buttons"; const styles = { panel: { @@ -39,14 +40,16 @@ type Props = DiffObjectProps & { }; type State = { - collapsed: boolean + collapsed: boolean, + sideBySide: boolean }; class DiffFile extends React.Component { constructor(props: Props) { super(props); this.state = { - collapsed: false + collapsed: false, + sideBySide: false }; } @@ -56,6 +59,12 @@ class DiffFile extends React.Component { })); }; + toggleSideBySide = () => { + this.setState(state => ({ + sideBySide: !state.sideBySide + })); + }; + setCollapse = (collapsed: boolean) => { this.setState({ collapsed @@ -149,10 +158,10 @@ class DiffFile extends React.Component { file, fileControlFactory, fileAnnotationFactory, - sideBySide, - classes + classes, + t } = this.props; - const { collapsed } = this.state; + const { collapsed, sideBySide } = this.state; const viewType = sideBySide ? "split" : "unified"; let body = null; @@ -173,14 +182,10 @@ class DiffFile extends React.Component { } const fileControls = fileControlFactory ? fileControlFactory(file, this.setCollapse) : null; - return ( -
+ return
-
+
{this.renderFileTitle(file)} @@ -189,12 +194,21 @@ class DiffFile extends React.Component { {this.renderChangeTag(file)}
-
{fileControls}
+
+ + {fileControls} +
{body} -
- ); +
; } } diff --git a/scm-ui/public/locales/de/repos.json b/scm-ui/public/locales/de/repos.json index 47353fa79b..f4ee071613 100644 --- a/scm-ui/public/locales/de/repos.json +++ b/scm-ui/public/locales/de/repos.json @@ -173,5 +173,9 @@ "submit": "Ja", "cancel": "Nein" } + }, + "diff": { + "sideBySide": "Zweispalitg", + "combined": "Kombiniert" } } diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 0d59b88819..714b7d4c14 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -181,6 +181,8 @@ "modify": "modified", "rename": "renamed", "copy": "copied" - } + }, + "sideBySide": "side-by-side", + "combined": "combined" } } From ae0feff3534670e5e80a04f18d1b6ebefd3fe8ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 11 Apr 2019 09:17:02 +0200 Subject: [PATCH 68/79] Fix test --- scm-ui/src/repos/branches/modules/branches.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scm-ui/src/repos/branches/modules/branches.test.js b/scm-ui/src/repos/branches/modules/branches.test.js index ef28a981c8..e708cb4993 100644 --- a/scm-ui/src/repos/branches/modules/branches.test.js +++ b/scm-ui/src/repos/branches/modules/branches.test.js @@ -415,19 +415,19 @@ describe("branches", () => { it("should return true if create branch is pending", () => { const state = { pending: { - [CREATE_BRANCH]: true + [CREATE_BRANCH + "/foo/bar"]: true } }; - expect(isCreateBranchPending(state)).toBe(true); + expect(isCreateBranchPending(state, repository)).toBe(true); }); it("should return false if create branch is not pending", () => { const state = { pending: { - [CREATE_BRANCH]: false + [CREATE_BRANCH + "/foo/bar"]: false } }; - expect(isCreateBranchPending(state)).toBe(false); + expect(isCreateBranchPending(state, repository)).toBe(false); }); it("should return error when create branch did fail", () => { From fbdbe4956fdc33a1173a66e6fef83ff48fd9a597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 11 Apr 2019 12:09:06 +0200 Subject: [PATCH 69/79] Fix permissions for repository git configuration --- .../sonia/scm/api/v2/resources/GitRepositoryConfigMapper.java | 2 +- .../sonia/scm/api/v2/resources/GitRepositoryConfigResource.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigMapper.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigMapper.java index 6480e526b1..9fde8f7d98 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigMapper.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigMapper.java @@ -28,7 +28,7 @@ public abstract class GitRepositoryConfigMapper { @AfterMapping void appendLinks(@MappingTarget GitRepositoryConfigDto target, @Context Repository repository) { Links.Builder linksBuilder = linkingTo().self(self()); - if (RepositoryPermissions.modify(repository).isPermitted()) { + if (RepositoryPermissions.custom("git", repository).isPermitted()) { linksBuilder.single(link("update", update())); } target.add(linksBuilder.build()); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java index 292a934ea0..175caf8840 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitRepositoryConfigResource.java @@ -70,7 +70,7 @@ public class GitRepositoryConfigResource { }) public Response setRepositoryConfig(@PathParam("namespace") String namespace, @PathParam("name") String name, GitRepositoryConfigDto dto) { Repository repository = getRepository(namespace, name); - RepositoryPermissions.modify(repository).check(); + RepositoryPermissions.custom("git", repository).check(); ConfigurationStore repositoryConfigStore = getStore(repository); GitRepositoryConfig config = repositoryConfigMapper.map(dto); repositoryConfigStore.set(config); From e1bd626eac5de3248f4bf6be18b5c4576e6ec5a9 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Thu, 11 Apr 2019 11:48:02 +0000 Subject: [PATCH 70/79] Close branch feature/side_by_side From e649a664231aeb2ba59f3e1c1e9dc8903e697cf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 11 Apr 2019 13:52:23 +0200 Subject: [PATCH 71/79] Fix unit test --- .../src/test/resources/sonia/scm/configuration/shiro.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/configuration/shiro.ini b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/configuration/shiro.ini index f0ffbe4ac5..083c685dbe 100644 --- a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/configuration/shiro.ini +++ b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/configuration/shiro.ini @@ -10,4 +10,4 @@ writer = configuration:write:git readerWriter = configuration:*:git,repository:*:id admin = * repoRead = repository:read:* -repoWrite = repository:modify:* +repoWrite = repository:modify:*,repository:git:* From 0ff1e2060c3eab21e8eef812b43476970c797326 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Thu, 11 Apr 2019 13:46:28 +0000 Subject: [PATCH 72/79] Close branch bugfix/repo_git_config_permissions From 1b608579599259341d1ae4f6928ac24b9dd794da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 12 Apr 2019 11:42:19 +0200 Subject: [PATCH 73/79] Introduce extension point for logout redirection --- .../api/v2/resources/LogoutRedirection.java | 11 +++++++ scm-ui/src/containers/Logout.js | 8 +++-- scm-ui/src/modules/auth.js | 30 ++++++++++++++----- .../v2/resources/AuthenticationResource.java | 18 +++++++++-- .../v2/resources/RedirectAfterLogoutDto.java | 10 +++++++ .../resources/AuthenticationResourceTest.java | 2 +- 6 files changed, 65 insertions(+), 14 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/api/v2/resources/LogoutRedirection.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/RedirectAfterLogoutDto.java diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/LogoutRedirection.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/LogoutRedirection.java new file mode 100644 index 0000000000..c0b59d4dac --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/LogoutRedirection.java @@ -0,0 +1,11 @@ +package sonia.scm.api.v2.resources; + +import sonia.scm.plugin.ExtensionPoint; + +import java.net.URI; +import java.util.Optional; + +@ExtensionPoint(multi = false) +public interface LogoutRedirection { + Optional afterLogoutRedirectTo(); +} diff --git a/scm-ui/src/containers/Logout.js b/scm-ui/src/containers/Logout.js index fe6662da42..465e1b156a 100644 --- a/scm-ui/src/containers/Logout.js +++ b/scm-ui/src/containers/Logout.js @@ -3,6 +3,7 @@ import React from "react"; import { connect } from "react-redux"; import { translate } from "react-i18next"; import { Redirect } from "react-router-dom"; +import type { History } from "history"; import { logout, @@ -20,15 +21,16 @@ type Props = { logoutLink: string, // dispatcher functions - logout: (link: string) => void, + logout: (link: string, history: History) => void, // context props + history: History, t: string => string }; class Logout extends React.Component { componentDidMount() { - this.props.logout(this.props.logoutLink); + this.props.logout(this.props.logoutLink, this.props.history); } render() { @@ -64,7 +66,7 @@ const mapStateToProps = state => { const mapDispatchToProps = dispatch => { return { - logout: (link: string) => dispatch(logout(link)) + logout: (link: string, history: History) => dispatch(logout(link, history)) }; }; diff --git a/scm-ui/src/modules/auth.js b/scm-ui/src/modules/auth.js index 4c0d22b23d..ecd529f735 100644 --- a/scm-ui/src/modules/auth.js +++ b/scm-ui/src/modules/auth.js @@ -11,6 +11,7 @@ import { fetchIndexResourcesPending, fetchIndexResourcesSuccess } from "./indexResource"; +import type { History } from "history"; // Action @@ -30,6 +31,10 @@ export const LOGOUT_PENDING = `${LOGOUT}_${types.PENDING_SUFFIX}`; export const LOGOUT_SUCCESS = `${LOGOUT}_${types.SUCCESS_SUFFIX}`; export const LOGOUT_FAILURE = `${LOGOUT}_${types.FAILURE_SUFFIX}`; +type LogoutRedirection = { + logoutRedirect: string +}; + // Reducer const initialState = {}; @@ -130,11 +135,9 @@ export const fetchMeFailure = (error: Error) => { // side effects const callFetchMe = (link: string): Promise => { - return apiClient - .get(link) - .then(response => { - return response.json(); - }); + return apiClient.get(link).then(response => { + return response.json(); + }); }; export const login = ( @@ -187,13 +190,24 @@ export const fetchMe = (link: string) => { }; }; -export const logout = (link: string) => { +export const logout = (link: string, history: History) => { return function(dispatch: any) { dispatch(logoutPending()); return apiClient .delete(link) - .then(() => { - dispatch(logoutSuccess()); + .then(response => { + return response.status === 200 + ? response.json() + : new Promise(function(resolve) { + resolve(undefined); + }); + }) + .then(json => { + if (json && json.logoutRedirect) { + location.href = json.logoutRedirect; + } else { + dispatch(logoutSuccess()); + } }) .then(() => { dispatch(fetchIndexResources()); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java index 47918080ab..a10375b83a 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java @@ -16,6 +16,8 @@ import javax.ws.rs.*; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import java.net.URI; +import java.util.Optional; @Path(AuthenticationResource.PATH) @AllowAnonymousAccess @@ -27,12 +29,14 @@ public class AuthenticationResource { private final AccessTokenBuilderFactory tokenBuilderFactory; private final AccessTokenCookieIssuer cookieIssuer; + private final LogoutRedirection logoutRedirection; @Inject - public AuthenticationResource(AccessTokenBuilderFactory tokenBuilderFactory, AccessTokenCookieIssuer cookieIssuer) + public AuthenticationResource(AccessTokenBuilderFactory tokenBuilderFactory, AccessTokenCookieIssuer cookieIssuer, LogoutRedirection logoutRedirection) { this.tokenBuilderFactory = tokenBuilderFactory; this.cookieIssuer = cookieIssuer; + this.logoutRedirection = logoutRedirection; } @@ -121,6 +125,7 @@ public class AuthenticationResource { @DELETE @Path("access_token") + @Produces(MediaType.APPLICATION_JSON) @StatusCodes({ @ResponseCode(code = 204, condition = "success"), @ResponseCode(code = 500, condition = "internal server error") @@ -135,7 +140,16 @@ public class AuthenticationResource { cookieIssuer.invalidate(request, response); // TODO anonymous access ?? - return Response.noContent().build(); + if (logoutRedirection == null) { + return Response.noContent().build(); + } else { + Optional uri = logoutRedirection.afterLogoutRedirectTo(); + if (uri.isPresent()) { + return Response.ok(new RedirectAfterLogoutDto(uri.get().toASCIIString())).build(); + } else { + return Response.noContent().build(); + } + } } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RedirectAfterLogoutDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RedirectAfterLogoutDto.java new file mode 100644 index 0000000000..a6402a0190 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RedirectAfterLogoutDto.java @@ -0,0 +1,10 @@ +package sonia.scm.api.v2.resources; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class RedirectAfterLogoutDto { + private String logoutRedirect; +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java index 1123dc94ce..3493283086 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java @@ -103,7 +103,7 @@ public class AuthenticationResourceTest { @Before public void prepareEnvironment() { - AuthenticationResource authenticationResource = new AuthenticationResource(accessTokenBuilderFactory, cookieIssuer); + AuthenticationResource authenticationResource = new AuthenticationResource(accessTokenBuilderFactory, cookieIssuer, null); dispatcher.getRegistry().addSingletonResource(authenticationResource); AccessToken accessToken = mock(AccessToken.class); From e56d6504c04ab684f9986dec788b013ae46a538a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 12 Apr 2019 14:32:17 +0200 Subject: [PATCH 74/79] Add unit tests --- .../api/v2/resources/LogoutRedirection.java | 1 + scm-ui/src/modules/auth.js | 5 +- scm-ui/src/modules/auth.test.js | 40 +++++++++++++++- .../resources/AuthenticationResourceTest.java | 46 ++++++++++++++++--- 4 files changed, 81 insertions(+), 11 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/LogoutRedirection.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/LogoutRedirection.java index c0b59d4dac..84fe2ddf7b 100644 --- a/scm-core/src/main/java/sonia/scm/api/v2/resources/LogoutRedirection.java +++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/LogoutRedirection.java @@ -6,6 +6,7 @@ import java.net.URI; import java.util.Optional; @ExtensionPoint(multi = false) +@FunctionalInterface public interface LogoutRedirection { Optional afterLogoutRedirectTo(); } diff --git a/scm-ui/src/modules/auth.js b/scm-ui/src/modules/auth.js index ecd529f735..8b22c8a0c5 100644 --- a/scm-ui/src/modules/auth.js +++ b/scm-ui/src/modules/auth.js @@ -204,10 +204,9 @@ export const logout = (link: string, history: History) => { }) .then(json => { if (json && json.logoutRedirect) { - location.href = json.logoutRedirect; - } else { - dispatch(logoutSuccess()); + window.location.assign(json.logoutRedirect); } + dispatch(logoutSuccess()); }) .then(() => { dispatch(fetchIndexResources()); diff --git a/scm-ui/src/modules/auth.test.js b/scm-ui/src/modules/auth.test.js index 1d7cb5096e..199d369022 100644 --- a/scm-ui/src/modules/auth.test.js +++ b/scm-ui/src/modules/auth.test.js @@ -26,7 +26,7 @@ import reducer, { FETCH_ME, LOGOUT, getLoginFailure, - getLogoutFailure + getLogoutFailure, } from "./auth"; import configureMockStore from "redux-mock-store"; @@ -224,6 +224,44 @@ describe("auth actions", () => { }); }); + it("should dispatch logout success and redirect", () => { + fetchMock.deleteOnce("/api/v2/auth/access_token", { + status: 200, + body: { logoutRedirect: "http://example.com/cas/logout" } + }); + + fetchMock.getOnce("/api/v2/me", { + status: 401 + }); + + fetchMock.getOnce("/api/v2/", { + _links: { + login: { + login: "/login" + } + } + }); + + window.location.assign = jest.fn(); + + const expectedActions = [ + { type: LOGOUT_PENDING }, + { type: LOGOUT_SUCCESS }, + { type: FETCH_INDEXRESOURCES_PENDING } + ]; + + const store = mockStore({}); + + return store.dispatch(logout("/auth/access_token")).then(() => { + expect(window.location.assign.mock.calls[0][0]).toBe( + "http://example.com/cas/logout" + ); + expect(store.getActions()).toEqual(expectedActions); + + // expect(window.location.href).toEqual("http://example.com/cas/logout"); + }); + }); + it("should dispatch logout failure", () => { fetchMock.deleteOnce("/api/v2/auth/access_token", { status: 500 diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java index 3493283086..b9612fedb2 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java @@ -23,10 +23,18 @@ import sonia.scm.security.DefaultAccessTokenCookieIssuer; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.core.MediaType; +import java.io.UnsupportedEncodingException; +import java.net.URI; import java.net.URISyntaxException; import java.util.Date; +import java.util.Optional; +import static java.net.URI.create; +import static java.util.Optional.empty; +import static java.util.Optional.of; +import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -49,6 +57,8 @@ public class AuthenticationResourceTest { private AccessTokenCookieIssuer cookieIssuer = new DefaultAccessTokenCookieIssuer(mock(ScmConfiguration.class)); + private MockHttpResponse response = new MockHttpResponse(); + private static final String AUTH_JSON_TRILLIAN = "{\n" + "\t\"cookie\": true,\n" + "\t\"grant_type\": \"password\",\n" + @@ -123,7 +133,6 @@ public class AuthenticationResourceTest { public void shouldAuthCorrectly() throws URISyntaxException { MockHttpRequest request = getMockHttpRequest(AUTH_JSON_TRILLIAN); - MockHttpResponse response = new MockHttpResponse(); dispatcher.invoke(request, response); @@ -134,7 +143,6 @@ public class AuthenticationResourceTest { public void shouldAuthCorrectlyWithFormencodedData() throws URISyntaxException { MockHttpRequest request = getMockHttpRequestUrlEncoded(AUTH_FORMENCODED_TRILLIAN); - MockHttpResponse response = new MockHttpResponse(); dispatcher.invoke(request, response); @@ -146,7 +154,6 @@ public class AuthenticationResourceTest { public void shouldNotAuthUserWithWrongPassword() throws URISyntaxException { MockHttpRequest request = getMockHttpRequest(AUTH_JSON_TRILLIAN_WRONG_PW); - MockHttpResponse response = new MockHttpResponse(); dispatcher.invoke(request, response); @@ -156,7 +163,6 @@ public class AuthenticationResourceTest { @Test public void shouldNotAuthNonexistingUser() throws URISyntaxException { MockHttpRequest request = getMockHttpRequest(AUTH_JSON_NOT_EXISTING_USER); - MockHttpResponse response = new MockHttpResponse(); dispatcher.invoke(request, response); @@ -187,16 +193,43 @@ public class AuthenticationResourceTest { @SubjectAware(username = "trillian", password = "secret") public void shouldSuccessfullyLogoutUser() throws URISyntaxException { MockHttpRequest request = MockHttpRequest.delete("/" + AuthenticationResource.PATH + "/access_token"); - MockHttpResponse response = new MockHttpResponse(); dispatcher.invoke(request, response); assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus()); } + @Test + public void shouldHandleLogoutRedirection() throws URISyntaxException, UnsupportedEncodingException { + mockResourceWithLogoutRedirection(of(create("http://example.com/cas/logout"))); + + MockHttpRequest request = MockHttpRequest.delete("/" + AuthenticationResource.PATH + "/access_token"); + + dispatcher.invoke(request, response); + + assertEquals(HttpServletResponse.SC_OK, response.getStatus()); + assertThat(response.getContentAsString(), containsString("http://example.com/cas/logout")); + } + + @Test + public void shouldHandleDisabledLogoutRedirection() throws URISyntaxException { + mockResourceWithLogoutRedirection(empty()); + + MockHttpRequest request = MockHttpRequest.delete("/" + AuthenticationResource.PATH + "/access_token"); + + dispatcher.invoke(request, response); + + assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus()); + } + + private void mockResourceWithLogoutRedirection(Optional target) { + dispatcher.getRegistry().removeRegistrations(AuthenticationResource.class); + AuthenticationResource authenticationResource = + new AuthenticationResource(accessTokenBuilderFactory, cookieIssuer, () -> target); + dispatcher.getRegistry().addSingletonResource(authenticationResource); + } private void shouldReturnBadRequest(String requestBody) throws URISyntaxException { MockHttpRequest request = getMockHttpRequest(requestBody); - MockHttpResponse response = new MockHttpResponse(); dispatcher.invoke(request, response); @@ -218,5 +251,4 @@ public class AuthenticationResourceTest { request.contentType(MediaType.APPLICATION_FORM_URLENCODED_TYPE); return request; } - } From 20cf0507a215b92f42796da4b1c05b25635ad460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 12 Apr 2019 15:26:22 +0200 Subject: [PATCH 75/79] Fix injection --- .../api/v2/resources/AuthenticationResource.java | 11 +++++++---- .../v2/resources/AuthenticationResourceTest.java | 15 +++++---------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java index a10375b83a..deab53a708 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java @@ -29,17 +29,17 @@ public class AuthenticationResource { private final AccessTokenBuilderFactory tokenBuilderFactory; private final AccessTokenCookieIssuer cookieIssuer; - private final LogoutRedirection logoutRedirection; + + @Inject(optional = true) + private LogoutRedirection logoutRedirection; @Inject - public AuthenticationResource(AccessTokenBuilderFactory tokenBuilderFactory, AccessTokenCookieIssuer cookieIssuer, LogoutRedirection logoutRedirection) + public AuthenticationResource(AccessTokenBuilderFactory tokenBuilderFactory, AccessTokenCookieIssuer cookieIssuer) { this.tokenBuilderFactory = tokenBuilderFactory; this.cookieIssuer = cookieIssuer; - this.logoutRedirection = logoutRedirection; } - @POST @Path("access_token") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @@ -152,4 +152,7 @@ public class AuthenticationResource { } } + void setLogoutRedirection(LogoutRedirection logoutRedirection) { + this.logoutRedirection = logoutRedirection; + } } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java index b9612fedb2..177f975971 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java @@ -111,9 +111,11 @@ public class AuthenticationResourceTest { "}" ); + private AuthenticationResource authenticationResource; + @Before public void prepareEnvironment() { - AuthenticationResource authenticationResource = new AuthenticationResource(accessTokenBuilderFactory, cookieIssuer, null); + authenticationResource = new AuthenticationResource(accessTokenBuilderFactory, cookieIssuer); dispatcher.getRegistry().addSingletonResource(authenticationResource); AccessToken accessToken = mock(AccessToken.class); @@ -200,7 +202,7 @@ public class AuthenticationResourceTest { @Test public void shouldHandleLogoutRedirection() throws URISyntaxException, UnsupportedEncodingException { - mockResourceWithLogoutRedirection(of(create("http://example.com/cas/logout"))); + authenticationResource.setLogoutRedirection(() -> of(create("http://example.com/cas/logout"))); MockHttpRequest request = MockHttpRequest.delete("/" + AuthenticationResource.PATH + "/access_token"); @@ -212,7 +214,7 @@ public class AuthenticationResourceTest { @Test public void shouldHandleDisabledLogoutRedirection() throws URISyntaxException { - mockResourceWithLogoutRedirection(empty()); + authenticationResource.setLogoutRedirection(Optional::empty); MockHttpRequest request = MockHttpRequest.delete("/" + AuthenticationResource.PATH + "/access_token"); @@ -221,13 +223,6 @@ public class AuthenticationResourceTest { assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus()); } - private void mockResourceWithLogoutRedirection(Optional target) { - dispatcher.getRegistry().removeRegistrations(AuthenticationResource.class); - AuthenticationResource authenticationResource = - new AuthenticationResource(accessTokenBuilderFactory, cookieIssuer, () -> target); - dispatcher.getRegistry().addSingletonResource(authenticationResource); - } - private void shouldReturnBadRequest(String requestBody) throws URISyntaxException { MockHttpRequest request = getMockHttpRequest(requestBody); From 2b2d7ea0e9b44eb56bbdb06f5821882e2b07f9e1 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 17 Apr 2019 11:45:44 +0200 Subject: [PATCH 76/79] do not show login error, when redirecting --- scm-ui/src/containers/Logout.js | 17 +++++++------ scm-ui/src/modules/auth.js | 43 ++++++++++++++++++++++++--------- scm-ui/src/modules/auth.test.js | 30 +++++++++++++++++++---- 3 files changed, 66 insertions(+), 24 deletions(-) diff --git a/scm-ui/src/containers/Logout.js b/scm-ui/src/containers/Logout.js index 465e1b156a..27c4902000 100644 --- a/scm-ui/src/containers/Logout.js +++ b/scm-ui/src/containers/Logout.js @@ -3,13 +3,12 @@ import React from "react"; import { connect } from "react-redux"; import { translate } from "react-i18next"; import { Redirect } from "react-router-dom"; -import type { History } from "history"; import { logout, isAuthenticated, isLogoutPending, - getLogoutFailure + getLogoutFailure, isRedirecting } from "../modules/auth"; import { Loading, ErrorPage } from "@scm-manager/ui-components"; import { getLogoutLink } from "../modules/indexResource"; @@ -17,24 +16,24 @@ import { getLogoutLink } from "../modules/indexResource"; type Props = { authenticated: boolean, loading: boolean, + redirecting: boolean, error: Error, logoutLink: string, // dispatcher functions - logout: (link: string, history: History) => void, + logout: (link: string) => void, // context props - history: History, t: string => string }; class Logout extends React.Component { componentDidMount() { - this.props.logout(this.props.logoutLink, this.props.history); + this.props.logout(this.props.logoutLink); } render() { - const { authenticated, loading, error, t } = this.props; + const { authenticated, redirecting, loading, error, t } = this.props; if (error) { return ( { error={error} /> ); - } else if (loading || authenticated) { + } else if (loading || authenticated || redirecting) { return ; } else { return ; @@ -54,11 +53,13 @@ class Logout extends React.Component { const mapStateToProps = state => { const authenticated = isAuthenticated(state); const loading = isLogoutPending(state); + const redirecting = isRedirecting(state); const error = getLogoutFailure(state); const logoutLink = getLogoutLink(state); return { authenticated, loading, + redirecting, error, logoutLink }; @@ -66,7 +67,7 @@ const mapStateToProps = state => { const mapDispatchToProps = dispatch => { return { - logout: (link: string, history: History) => dispatch(logout(link, history)) + logout: (link: string) => dispatch(logout(link)) }; }; diff --git a/scm-ui/src/modules/auth.js b/scm-ui/src/modules/auth.js index 8b22c8a0c5..73ba013fa1 100644 --- a/scm-ui/src/modules/auth.js +++ b/scm-ui/src/modules/auth.js @@ -11,7 +11,6 @@ import { fetchIndexResourcesPending, fetchIndexResourcesSuccess } from "./indexResource"; -import type { History } from "history"; // Action @@ -30,10 +29,7 @@ export const LOGOUT = "scm/auth/LOGOUT"; export const LOGOUT_PENDING = `${LOGOUT}_${types.PENDING_SUFFIX}`; export const LOGOUT_SUCCESS = `${LOGOUT}_${types.SUCCESS_SUFFIX}`; export const LOGOUT_FAILURE = `${LOGOUT}_${types.FAILURE_SUFFIX}`; - -type LogoutRedirection = { - logoutRedirect: string -}; +export const LOGOUT_REDIRECT = `${LOGOUT}_REDIRECT`; // Reducer @@ -59,6 +55,13 @@ export default function reducer( case LOGOUT_SUCCESS: return initialState; + case LOGOUT_REDIRECT: { + // we keep the current state until we are redirected to the new page + return { + ...state, + redirecting: true + }; + } default: return state; } @@ -94,10 +97,16 @@ export const logoutPending = () => { export const logoutSuccess = () => { return { - type: LOGOUT_SUCCESS + type: LOGOUT_SUCCESS, }; }; +export const redirectAfterLogout = () => { + return { + type: LOGOUT_REDIRECT + } +}; + export const logoutFailure = (error: Error) => { return { type: LOGOUT_FAILURE, @@ -190,7 +199,7 @@ export const fetchMe = (link: string) => { }; }; -export const logout = (link: string, history: History) => { +export const logout = (link: string) => { return function(dispatch: any) { dispatch(logoutPending()); return apiClient @@ -199,17 +208,24 @@ export const logout = (link: string, history: History) => { return response.status === 200 ? response.json() : new Promise(function(resolve) { - resolve(undefined); + resolve(); }); }) .then(json => { + let fetchIndex = true; if (json && json.logoutRedirect) { + dispatch(redirectAfterLogout()); window.location.assign(json.logoutRedirect); + fetchIndex = false; + } else { + dispatch(logoutSuccess()); } - dispatch(logoutSuccess()); + return fetchIndex; }) - .then(() => { - dispatch(fetchIndexResources()); + .then((fetchIndex: boolean) => { + if (fetchIndex) { + dispatch(fetchIndexResources()); + } }) .catch(error => { dispatch(logoutFailure(error)); @@ -257,3 +273,8 @@ export const isLogoutPending = (state: Object) => { export const getLogoutFailure = (state: Object) => { return getFailure(state, LOGOUT); }; + +export const isRedirecting = (state: Object) => { + return !!stateAuth(state).redirecting; +}; + diff --git a/scm-ui/src/modules/auth.test.js b/scm-ui/src/modules/auth.test.js index 199d369022..cdf8e7c661 100644 --- a/scm-ui/src/modules/auth.test.js +++ b/scm-ui/src/modules/auth.test.js @@ -26,7 +26,7 @@ import reducer, { FETCH_ME, LOGOUT, getLoginFailure, - getLogoutFailure, + getLogoutFailure, isRedirecting, LOGOUT_REDIRECT, redirectAfterLogout, } from "./auth"; import configureMockStore from "redux-mock-store"; @@ -70,6 +70,17 @@ describe("auth reducer", () => { expect(state.authenticated).toBeUndefined(); }); + it("should keep state and set redirecting to true", () => { + const initialState = { + authenticated: true, + me + }; + const state = reducer(initialState, redirectAfterLogout()); + expect(state.me).toBe(initialState.me); + expect(state.authenticated).toBe(initialState.authenticated); + expect(state.redirecting).toBe(true); + }); + it("should set state authenticated and me after login", () => { const state = reducer(undefined, loginSuccess(me)); expect(state.me).toBe(me); @@ -246,8 +257,7 @@ describe("auth actions", () => { const expectedActions = [ { type: LOGOUT_PENDING }, - { type: LOGOUT_SUCCESS }, - { type: FETCH_INDEXRESOURCES_PENDING } + { type: LOGOUT_REDIRECT } ]; const store = mockStore({}); @@ -257,8 +267,6 @@ describe("auth actions", () => { "http://example.com/cas/logout" ); expect(store.getActions()).toEqual(expectedActions); - - // expect(window.location.href).toEqual("http://example.com/cas/logout"); }); }); @@ -345,4 +353,16 @@ describe("auth selectors", () => { it("should return unknown, if failure state is not set for LOGOUT", () => { expect(getLogoutFailure({})).toBeUndefined(); }); + + it("should return false, if redirecting is not set", () => { + expect(isRedirecting({})).toBe(false); + }); + + it("should return false, if redirecting is false", () => { + expect(isRedirecting({auth: { redirecting: false }})).toBe(false); + }); + + it("should return true, if redirecting is true", () => { + expect(isRedirecting({auth: { redirecting: true }})).toBe(true); + }); }); From 5cdb7bec32aebf2e503f9d704ca9e319b037a7f3 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 17 Apr 2019 12:44:41 +0200 Subject: [PATCH 77/79] memoize getBranches to fix reference checks of react --- scm-ui/package.json | 3 ++- scm-ui/src/repos/branches/modules/branches.js | 14 +++++++--- .../repos/branches/modules/branches.test.js | 27 ++++++++++++++++++- scm-ui/yarn.lock | 4 +++ 4 files changed, 43 insertions(+), 5 deletions(-) diff --git a/scm-ui/package.json b/scm-ui/package.json index 65b4b113b4..3c730540ae 100644 --- a/scm-ui/package.json +++ b/scm-ui/package.json @@ -17,11 +17,12 @@ "i18next-browser-languagedetector": "^2.2.2", "i18next-fetch-backend": "^0.1.0", "jss-nested": "^6.0.1", + "memoize-one": "^5.0.4", "moment": "^2.22.2", "node-sass": "^4.9.3", "postcss-easy-import": "^3.0.0", - "react": "^16.8.6", "query-string": "5", + "react": "^16.8.6", "react-diff-view": "^1.8.1", "react-dom": "^16.8.6", "react-i18next": "^7.9.0", diff --git a/scm-ui/src/repos/branches/modules/branches.js b/scm-ui/src/repos/branches/modules/branches.js index 054a303a05..7f8c45932a 100644 --- a/scm-ui/src/repos/branches/modules/branches.js +++ b/scm-ui/src/repos/branches/modules/branches.js @@ -15,6 +15,8 @@ import type { import { isPending } from "../../../modules/pending"; import { getFailure } from "../../../modules/failure"; +import memoizeOne from 'memoize-one'; + export const FETCH_BRANCHES = "scm/repos/FETCH_BRANCHES"; export const FETCH_BRANCHES_PENDING = `${FETCH_BRANCHES}_${PENDING_SUFFIX}`; export const FETCH_BRANCHES_SUCCESS = `${FETCH_BRANCHES}_${SUCCESS_SUFFIX}`; @@ -108,12 +110,18 @@ export function createBranch( // Selectors +function collectBranches(repoState) { + return repoState.list._embedded.branches.map( + name => repoState.byName[name] + ); +} + +const memoizedBranchCollector = memoizeOne(collectBranches); + export function getBranches(state: Object, repository: Repository) { const repoState = getRepoState(state, repository); if (repoState && repoState.list) { - return repoState.list._embedded.branches.map( - name => repoState.byName[name] - ); + return memoizedBranchCollector(repoState); } } diff --git a/scm-ui/src/repos/branches/modules/branches.test.js b/scm-ui/src/repos/branches/modules/branches.test.js index e708cb4993..3d2b69dce4 100644 --- a/scm-ui/src/repos/branches/modules/branches.test.js +++ b/scm-ui/src/repos/branches/modules/branches.test.js @@ -348,7 +348,32 @@ describe("branches", () => { it("should return always the same reference for branches", () => { const one = getBranches(state, repository); const two = getBranches(state, repository); - expect(one).toEqual(two); + expect(one).toBe(two); + }); + + it("should not return cached reference, if branches have changed", () => { + const one = getBranches(state, repository); + const newState = { + branches: { + "foo/bar": { + list: { + _links: {}, + _embedded: { + branches: ["branch2", "branch3"] + } + }, + byName: { + branch2, + branch3 + } + } + } + }; + const two = getBranches(newState, repository); + expect(one).not.toBe(two); + expect(two).not.toContain(branch1); + expect(two).toContain(branch2); + expect(two).toContain(branch3); }); it("should return undefined, if no branches for the repository available", () => { diff --git a/scm-ui/yarn.lock b/scm-ui/yarn.lock index 67643924ba..3986930a35 100644 --- a/scm-ui/yarn.lock +++ b/scm-ui/yarn.lock @@ -5669,6 +5669,10 @@ memoize-one@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-4.0.3.tgz#cdfdd942853f1a1b4c71c5336b8c49da0bf0273c" +memoize-one@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.4.tgz#005928aced5c43d890a4dfab18ca908b0ec92cbc" + memoizee@0.4.X: version "0.4.14" resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57" From 7b167f9cbb38f439428322a50c82bec09edf7581 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 17 Apr 2019 10:46:33 +0000 Subject: [PATCH 78/79] Close branch feature/logout_redirection From ffd2f589ca2a52c0eb8bd8e469786caeb17d0651 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 17 Apr 2019 11:29:46 +0000 Subject: [PATCH 79/79] Close branch feature/create-branch