mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-02 03:25:56 +01:00
Fixed issue with slashes in branch names
This commit is contained in:
129
scm-ui-components/packages/ui-components/src/LinkPaginator.js
Normal file
129
scm-ui-components/packages/ui-components/src/LinkPaginator.js
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
//@flow
|
||||||
|
import React from "react";
|
||||||
|
import {translate} from "react-i18next";
|
||||||
|
import type {PagedCollection} from "@scm-manager/ui-types";
|
||||||
|
import {Button} from "./buttons";
|
||||||
|
import {withRouter} from "react-router-dom";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
collection: PagedCollection,
|
||||||
|
t: string => string,
|
||||||
|
match: any
|
||||||
|
};
|
||||||
|
|
||||||
|
class LinkPaginator extends React.Component<Props> {
|
||||||
|
renderFirstButton() {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={"pagination-link"}
|
||||||
|
label={"1"}
|
||||||
|
disabled={false}
|
||||||
|
link={"1"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPreviousButton(label?: string) {
|
||||||
|
const { match } = this.props;
|
||||||
|
const page = parseInt(match.params.page) || 1;
|
||||||
|
const previousPage = page - 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={"pagination-previous"}
|
||||||
|
label={label ? label : previousPage.toString()}
|
||||||
|
disabled={previousPage < 1}
|
||||||
|
link={`${previousPage}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderNextButton(label?: string) {
|
||||||
|
const { match, collection } = this.props;
|
||||||
|
let page = parseInt(match.params.page) || 1;
|
||||||
|
|
||||||
|
const nextPage = page + 1;
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={"pagination-next"}
|
||||||
|
label={label ? label : nextPage.toString()}
|
||||||
|
disabled={nextPage >= collection.pageTotal + 1}
|
||||||
|
link={`${nextPage}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderLastButton() {
|
||||||
|
const { collection } = this.props;
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={"pagination-link"}
|
||||||
|
label={`${collection.pageTotal}`}
|
||||||
|
disabled={false}
|
||||||
|
link={`${collection.pageTotal}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
separator() {
|
||||||
|
return <span className="pagination-ellipsis">…</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPage(page: number) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className="pagination-link is-current"
|
||||||
|
label={page}
|
||||||
|
disabled={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pageLinks() {
|
||||||
|
const { collection } = this.props;
|
||||||
|
|
||||||
|
const links = [];
|
||||||
|
const page = collection.page + 1;
|
||||||
|
const pageTotal = collection.pageTotal;
|
||||||
|
if (page > 1) {
|
||||||
|
links.push(this.renderFirstButton());
|
||||||
|
}
|
||||||
|
if (page > 3) {
|
||||||
|
links.push(this.separator());
|
||||||
|
}
|
||||||
|
if (page > 2) {
|
||||||
|
links.push(this.renderPreviousButton());
|
||||||
|
}
|
||||||
|
|
||||||
|
links.push(this.currentPage(page));
|
||||||
|
|
||||||
|
if (page + 1 < pageTotal) {
|
||||||
|
links.push(this.renderNextButton());
|
||||||
|
}
|
||||||
|
if (page + 2 < pageTotal)
|
||||||
|
//if there exists pages between next and last
|
||||||
|
links.push(this.separator());
|
||||||
|
if (page < pageTotal) {
|
||||||
|
links.push(this.renderLastButton());
|
||||||
|
}
|
||||||
|
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { t } = this.props;
|
||||||
|
return (
|
||||||
|
<nav className="pagination is-centered" aria-label="pagination">
|
||||||
|
{this.renderPreviousButton(t("paginator.previous"))}
|
||||||
|
<ul className="pagination-list">
|
||||||
|
{this.pageLinks().map((link, index) => {
|
||||||
|
return <li key={index}>{link}</li>;
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
{this.renderNextButton(t("paginator.next"))}
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withRouter(translate("commons")(LinkPaginator));
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Link } from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
|
|
||||||
export type ButtonProps = {
|
export type ButtonProps = {
|
||||||
label: string,
|
label: string,
|
||||||
@@ -59,7 +59,7 @@ class Button extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { link } = this.props;
|
const { link } = this.props;
|
||||||
if (link) {
|
if (link) {
|
||||||
return <Link to={link}>{this.renderButton()}</Link>;
|
return <Link to={link}>{this.renderButton()}</Link>; // TODO: className does only apply to button, not the Link
|
||||||
} else {
|
} else {
|
||||||
return this.renderButton();
|
return this.renderButton();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export { default as Logo } from "./Logo.js";
|
|||||||
export { default as MailLink } from "./MailLink.js";
|
export { default as MailLink } from "./MailLink.js";
|
||||||
export { default as Notification } from "./Notification.js";
|
export { default as Notification } from "./Notification.js";
|
||||||
export { default as Paginator } from "./Paginator.js";
|
export { default as Paginator } from "./Paginator.js";
|
||||||
|
export { default as LinkPaginator } from "./LinkPaginator.js";
|
||||||
export { default as ProtectedRoute } from "./ProtectedRoute.js";
|
export { default as ProtectedRoute } from "./ProtectedRoute.js";
|
||||||
|
|
||||||
export { apiClient, NOT_FOUND_ERROR, UNAUTHORIZED_ERROR } from "./apiclient.js";
|
export { apiClient, NOT_FOUND_ERROR, UNAUTHORIZED_ERROR } from "./apiclient.js";
|
||||||
|
|||||||
@@ -1,89 +1,125 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
import * as React from "react";
|
||||||
import React from "react";
|
import type { Branch, Repository } from "@scm-manager/ui-types";
|
||||||
import type { Repository, Branch } from "@scm-manager/ui-types";
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import {
|
import {
|
||||||
fetchBranches,
|
fetchBranches,
|
||||||
getBranches,
|
getBranch,
|
||||||
|
getBranchNames,
|
||||||
|
getFetchBranchesFailure,
|
||||||
isFetchBranchesPending
|
isFetchBranchesPending
|
||||||
} from "../modules/branches";
|
} from "../modules/branches";
|
||||||
|
|
||||||
import { Loading } from "@scm-manager/ui-components";
|
|
||||||
import DropDown from "../components/DropDown";
|
import DropDown from "../components/DropDown";
|
||||||
|
import type { History } from "history";
|
||||||
|
import { withRouter } from "react-router-dom";
|
||||||
|
import { ErrorPage, Loading } from "@scm-manager/ui-components";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
repository: Repository,
|
repository: Repository,
|
||||||
fetchBranches: Repository => void,
|
|
||||||
callback: (?Branch) => void,
|
|
||||||
branches: Branch[],
|
branches: Branch[],
|
||||||
selectedBranchName: string,
|
branchNames: string[],
|
||||||
|
fetchBranches: Repository => void,
|
||||||
|
history: History,
|
||||||
|
match: any,
|
||||||
|
selectedBranch?: Branch,
|
||||||
|
label: string, //TODO: Should this be here?
|
||||||
loading: boolean,
|
loading: boolean,
|
||||||
label: string
|
branchSelected: string => void,
|
||||||
};
|
error: Error,
|
||||||
|
children: React.Node
|
||||||
type State = {
|
|
||||||
selectedBranchName: string
|
|
||||||
};
|
};
|
||||||
|
type State = {};
|
||||||
|
|
||||||
class BranchChooser extends React.Component<Props, State> {
|
class BranchChooser extends React.Component<Props, State> {
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
selectedBranchName: props.selectedBranchName
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { repository, fetchBranches } = this.props;
|
this.props.fetchBranches(this.props.repository);
|
||||||
fetchBranches(repository);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { branches, loading, label } = this.props;
|
const { selectedBranch, loading, error } = this.props;
|
||||||
if (loading) {
|
|
||||||
return <Loading />;
|
// TODO: i18n
|
||||||
}
|
if (error) {
|
||||||
if (branches && branches.length > 0) {
|
|
||||||
return (
|
return (
|
||||||
<div className={"box"}>
|
<ErrorPage
|
||||||
<label className="label">{label}</label>
|
title={"Failed loading branches"}
|
||||||
<DropDown
|
subtitle={"Somethin went wrong"}
|
||||||
options={branches.map(b => b.name)}
|
error={error}
|
||||||
preselectedOption={this.state.selectedBranchName}
|
/>
|
||||||
optionSelected={branch => this.branchChanged(branch)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
if (loading) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const childrenWithBranch = React.Children.map(
|
||||||
|
this.props.children,
|
||||||
|
child => {
|
||||||
|
return React.cloneElement(child, {
|
||||||
|
branch: selectedBranch
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{this.renderBranchChooser()}
|
||||||
|
{childrenWithBranch}
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
branchChanged = (branchName: string) => {
|
renderBranchChooser() {
|
||||||
const { callback } = this.props;
|
const { branchNames, label, branchSelected, match } = this.props;
|
||||||
this.setState({ ...this.state, selectedBranchName: branchName });
|
const selectedBranchName = match.params.branch;
|
||||||
const branch = this.props.branches.find(b => b.name === branchName);
|
|
||||||
callback(branch);
|
if (!branchNames || branchNames.length === 0) {
|
||||||
};
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={"box"}>
|
||||||
|
<label className="label">{label}</label>
|
||||||
|
<DropDown
|
||||||
|
options={branchNames}
|
||||||
|
preselectedOption={selectedBranchName}
|
||||||
|
optionSelected={branch => branchSelected(branch)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: State, ownProps: Props) => {
|
const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
branches: getBranches(state, ownProps.repository),
|
fetchBranches: (repo: Repository) => {
|
||||||
loading: isFetchBranchesPending(state, ownProps.repository)
|
dispatch(fetchBranches(repo));
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: any) => {
|
|
||||||
return {
|
|
||||||
fetchBranches: (repository: Repository) => {
|
|
||||||
dispatch(fetchBranches(repository));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(
|
const mapStateToProps = (state: any, ownProps: Props) => {
|
||||||
mapStateToProps,
|
const { repository, match } = ownProps;
|
||||||
mapDispatchToProps
|
const loading = isFetchBranchesPending(state, repository);
|
||||||
)(BranchChooser);
|
const error = getFetchBranchesFailure(state, repository);
|
||||||
|
const branchNames = getBranchNames(state, repository);
|
||||||
|
const selectedBranch = getBranch(
|
||||||
|
state,
|
||||||
|
repository,
|
||||||
|
decodeURIComponent(match.params.branch)
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
branchNames,
|
||||||
|
selectedBranch,
|
||||||
|
error
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withRouter(
|
||||||
|
connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(BranchChooser)
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,247 +1,124 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React from "react";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { translate } from "react-i18next";
|
|
||||||
import {
|
|
||||||
ErrorNotification,
|
|
||||||
Loading,
|
|
||||||
Paginator
|
|
||||||
} from "@scm-manager/ui-components";
|
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { withRouter } from "react-router-dom";
|
||||||
|
import type {
|
||||||
|
Branch,
|
||||||
|
Changeset,
|
||||||
|
PagedCollection,
|
||||||
|
Repository
|
||||||
|
} from "@scm-manager/ui-types";
|
||||||
import {
|
import {
|
||||||
fetchChangesets,
|
fetchChangesetsByBranch,
|
||||||
fetchChangesetsByBranchAndPage,
|
fetchChangesetsByBranchAndPage,
|
||||||
fetchChangesetsByLink,
|
|
||||||
fetchChangesetsByPage,
|
|
||||||
getChangesets,
|
getChangesets,
|
||||||
getChangesetsFromState,
|
|
||||||
getFetchChangesetsFailure,
|
getFetchChangesetsFailure,
|
||||||
isFetchChangesetsPending,
|
isFetchChangesetsPending,
|
||||||
selectListAsCollection
|
selectListAsCollection
|
||||||
} from "../modules/changesets";
|
} from "../modules/changesets";
|
||||||
import type { History } from "history";
|
import { connect } from "react-redux";
|
||||||
import type {
|
|
||||||
Changeset,
|
|
||||||
PagedCollection,
|
|
||||||
Repository,
|
|
||||||
Branch
|
|
||||||
} from "@scm-manager/ui-types";
|
|
||||||
import ChangesetList from "../components/changesets/ChangesetList";
|
import ChangesetList from "../components/changesets/ChangesetList";
|
||||||
import { withRouter } from "react-router-dom";
|
import { ErrorPage, LinkPaginator, Loading } from "@scm-manager/ui-components";
|
||||||
import { fetchBranches, getBranch, getBranchNames } from "../modules/branches";
|
|
||||||
import BranchChooser from "./BranchChooser";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
repository: Repository,
|
fetchChangesetsByBranch: (Repository, Branch) => void,
|
||||||
branchName: string,
|
|
||||||
branchNames: string[],
|
|
||||||
history: History,
|
|
||||||
fetchChangesetsByNamespaceNameAndBranch: (
|
|
||||||
namespace: string,
|
|
||||||
name: string,
|
|
||||||
branch: string
|
|
||||||
) => void,
|
|
||||||
list: PagedCollection,
|
|
||||||
fetchChangesetsByLink: (Repository, string, Branch) => void,
|
|
||||||
fetchChangesetsByPage: (Repository, number) => void,
|
|
||||||
fetchChangesetsByBranchAndPage: (Repository, Branch, number) => void,
|
fetchChangesetsByBranchAndPage: (Repository, Branch, number) => void,
|
||||||
fetchBranches: Repository => void,
|
repository: Repository, //TODO: Do we really need/want this here?
|
||||||
page: number,
|
branch: Branch,
|
||||||
t: string => string,
|
|
||||||
match: any,
|
|
||||||
changesets: Changeset[],
|
changesets: Changeset[],
|
||||||
loading: boolean,
|
loading: boolean,
|
||||||
error: boolean,
|
match: any,
|
||||||
branch: Branch
|
list: PagedCollection,
|
||||||
|
error: Error
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {};
|
type State = {};
|
||||||
|
|
||||||
class Changesets extends React.PureComponent<Props, State> {
|
class ChangesetContainer extends React.Component<Props, State> {
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
onPageChange = (link: string) => {
|
|
||||||
const { repository, branch } = this.props;
|
|
||||||
this.props.fetchChangesetsByLink(repository, link, branch);
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (!this.props.loading) {
|
|
||||||
this.updateContent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateContent() {
|
|
||||||
const {
|
const {
|
||||||
|
fetchChangesetsByBranch,
|
||||||
|
fetchChangesetsByBranchAndPage,
|
||||||
repository,
|
repository,
|
||||||
branch,
|
branch,
|
||||||
page,
|
match
|
||||||
fetchChangesetsByPage,
|
|
||||||
fetchChangesetsByBranchAndPage
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
if (branch) {
|
const { page } = match.params;
|
||||||
fetchChangesetsByBranchAndPage(repository, branch, page);
|
if (!page) {
|
||||||
|
fetchChangesetsByBranch(repository, branch);
|
||||||
} else {
|
} else {
|
||||||
fetchChangesetsByPage(repository, page);
|
fetchChangesetsByBranchAndPage(repository, branch, page);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Props) {
|
|
||||||
const { page, list, repository, match } = this.props;
|
|
||||||
const { namespace, name } = repository;
|
|
||||||
const branch = match.params.branch;
|
|
||||||
|
|
||||||
if (!this.props.loading) {
|
|
||||||
if (prevProps.branch !== this.props.branch) {
|
|
||||||
this.updateContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (list && (list.page || list.page === 0)) {
|
|
||||||
console.log(list);
|
|
||||||
// backend starts paging at 0
|
|
||||||
const statePage: number = list.page + 1;
|
|
||||||
console.log(`page: ${page} - statePage: ${statePage}`);
|
|
||||||
if (page !== statePage) {
|
|
||||||
if (branch) {
|
|
||||||
this.props.history.push(
|
|
||||||
`/repo/${namespace}/${name}/${branch}/changesets/${statePage}`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.props.history.push(
|
|
||||||
`/repo/${namespace}/${name}/changesets/${statePage}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { changesets, loading, error } = this.props;
|
const { changesets, loading, error } = this.props;
|
||||||
|
|
||||||
|
// TODO: i18n
|
||||||
if (error) {
|
if (error) {
|
||||||
return <ErrorNotification error={error} />;
|
return (
|
||||||
|
<ErrorPage
|
||||||
|
title={"Failed loading branches"}
|
||||||
|
subtitle={"Somethin went wrong"}
|
||||||
|
error={error}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading || !changesets) {
|
if (loading) {
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
}
|
}
|
||||||
|
if (!changesets || changesets.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
{this.renderList()}
|
{this.renderList()}
|
||||||
{this.renderPaginator()}
|
{this.renderPaginator()}
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderList = () => {
|
renderList = () => {
|
||||||
const branch = decodeURIComponent(this.props.match.params.branch);
|
const { repository, changesets } = this.props;
|
||||||
const { repository, changesets, t } = this.props;
|
return <ChangesetList repository={repository} changesets={changesets} />;
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<BranchChooser
|
|
||||||
repository={repository}
|
|
||||||
selectedBranchName={branch}
|
|
||||||
label={t("changesets.branchselector-label")}
|
|
||||||
callback={branch => this.branchChanged(branch)}
|
|
||||||
/>
|
|
||||||
<ChangesetList repository={repository} changesets={changesets} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
renderPaginator() {
|
renderPaginator = () => {
|
||||||
const { list } = this.props;
|
const { list } = this.props;
|
||||||
if (list) {
|
if (list) {
|
||||||
return <Paginator collection={list} onPageChange={this.onPageChange} />;
|
return <LinkPaginator collection={list} />;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
branchChanged = (branch: Branch): void => {
|
|
||||||
const { history, repository } = this.props;
|
|
||||||
if (branch === undefined) {
|
|
||||||
history.push(
|
|
||||||
`/repo/${repository.namespace}/${repository.name}/changesets`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const branchName = encodeURIComponent(branch.name);
|
|
||||||
this.setState({ branch: branchName });
|
|
||||||
history.push(
|
|
||||||
`/repo/${repository.namespace}/${
|
|
||||||
repository.name
|
|
||||||
}/${branchName}/changesets`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPageFromProps = props => {
|
|
||||||
let page = props.match.params.page;
|
|
||||||
if (page) {
|
|
||||||
page = parseInt(page, 10);
|
|
||||||
} else {
|
|
||||||
page = 1;
|
|
||||||
}
|
|
||||||
return page;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps: Props) => {
|
|
||||||
const { repository } = ownProps;
|
|
||||||
const branchName = ownProps.match.params.branch;
|
|
||||||
const branch = getBranch(state, repository, decodeURIComponent(branchName));
|
|
||||||
const loading = isFetchChangesetsPending(state, repository, branch);
|
|
||||||
const changesets = getChangesets(state, repository, branch);
|
|
||||||
const branchNames = getBranchNames(state, repository);
|
|
||||||
const error = getFetchChangesetsFailure(state, repository, branch);
|
|
||||||
const list = selectListAsCollection(state, repository);
|
|
||||||
const page = getPageFromProps(ownProps);
|
|
||||||
|
|
||||||
return {
|
|
||||||
loading,
|
|
||||||
changesets,
|
|
||||||
branchNames,
|
|
||||||
error,
|
|
||||||
list,
|
|
||||||
page,
|
|
||||||
branch
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
fetchBranches: (repository: Repository) => {
|
fetchChangesetsByBranch: (repo: Repository, branch: Branch) => {
|
||||||
dispatch(fetchBranches(repository));
|
dispatch(fetchChangesetsByBranch(repo, branch));
|
||||||
},
|
|
||||||
fetchChangesets: (repository: Repository) => {
|
|
||||||
dispatch(fetchChangesets(repository));
|
|
||||||
},
|
|
||||||
fetchChangesetsByPage: (repository, page: number) => {
|
|
||||||
dispatch(fetchChangesetsByPage(repository, page));
|
|
||||||
},
|
},
|
||||||
fetchChangesetsByBranchAndPage: (
|
fetchChangesetsByBranchAndPage: (
|
||||||
repository,
|
repo: Repository,
|
||||||
branch: Branch,
|
branch: Branch,
|
||||||
page: number
|
page: number
|
||||||
) => {
|
) => {
|
||||||
dispatch(fetchChangesetsByBranchAndPage(repository, branch, page));
|
dispatch(fetchChangesetsByBranchAndPage(repo, branch, page));
|
||||||
},
|
|
||||||
fetchChangesetsByLink: (
|
|
||||||
repository: Repository,
|
|
||||||
link: string,
|
|
||||||
branch?: Branch
|
|
||||||
) => {
|
|
||||||
dispatch(fetchChangesetsByLink(repository, link, branch));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (state: any, ownProps: Props) => {
|
||||||
|
const { repository, branch } = ownProps;
|
||||||
|
const changesets = getChangesets(state, repository, branch);
|
||||||
|
const loading = isFetchChangesetsPending(state, repository, branch);
|
||||||
|
const error = getFetchChangesetsFailure(state, repository, branch);
|
||||||
|
const list = selectListAsCollection(state, repository, branch);
|
||||||
|
return { changesets, list, loading, error };
|
||||||
|
};
|
||||||
export default withRouter(
|
export default withRouter(
|
||||||
connect(
|
connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(translate("repos")(Changesets))
|
)(ChangesetContainer)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ import { connect } from "react-redux";
|
|||||||
import { Route } from "react-router-dom";
|
import { Route } from "react-router-dom";
|
||||||
import type { Repository } from "@scm-manager/ui-types";
|
import type { Repository } from "@scm-manager/ui-types";
|
||||||
import {
|
import {
|
||||||
Page,
|
|
||||||
Loading,
|
|
||||||
ErrorPage,
|
ErrorPage,
|
||||||
|
Loading,
|
||||||
Navigation,
|
Navigation,
|
||||||
NavLink,
|
NavLink,
|
||||||
|
Page,
|
||||||
Section
|
Section
|
||||||
} from "@scm-manager/ui-components";
|
} from "@scm-manager/ui-components";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
@@ -25,6 +25,7 @@ import Edit from "../containers/Edit";
|
|||||||
|
|
||||||
import type { History } from "history";
|
import type { History } from "history";
|
||||||
import EditNavLink from "../components/EditNavLink";
|
import EditNavLink from "../components/EditNavLink";
|
||||||
|
import BranchChooser from "./BranchChooser";
|
||||||
import Changesets from "./Changesets";
|
import Changesets from "./Changesets";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -76,6 +77,17 @@ class RepositoryRoot extends React.Component<Props> {
|
|||||||
return route.location.pathname.match(regex);
|
return route.location.pathname.match(regex);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
branchSelected = (branchName: string) => {
|
||||||
|
const url = this.matchedUrl();
|
||||||
|
if (branchName === "") {
|
||||||
|
this.props.history.push(`${url}/changesets/`);
|
||||||
|
} else {
|
||||||
|
this.props.history.push(
|
||||||
|
`${url}/branches/${encodeURIComponent(branchName)}/changesets/`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { loading, error, repository, t } = this.props;
|
const { loading, error, repository, t } = this.props;
|
||||||
|
|
||||||
@@ -94,7 +106,7 @@ class RepositoryRoot extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const url = this.matchedUrl();
|
const url = this.matchedUrl();
|
||||||
|
// TODO: Changesets need to be adjusted (i.e. sub-routes need to be handled in sub-components)
|
||||||
return (
|
return (
|
||||||
<Page title={repository.namespace + "/" + repository.name}>
|
<Page title={repository.namespace + "/" + repository.name}>
|
||||||
<div className="columns">
|
<div className="columns">
|
||||||
@@ -108,25 +120,31 @@ class RepositoryRoot extends React.Component<Props> {
|
|||||||
path={`${url}/edit`}
|
path={`${url}/edit`}
|
||||||
component={() => <Edit repository={repository} />}
|
component={() => <Edit repository={repository} />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
exact
|
path={`${url}/changesets/:page?`}
|
||||||
path={`${url}/changesets`}
|
component={() => (
|
||||||
render={() => <Changesets repository={repository} />}
|
<BranchChooser
|
||||||
|
repository={repository}
|
||||||
|
label={"Branches"}
|
||||||
|
branchSelected={this.branchSelected}
|
||||||
|
>
|
||||||
|
<Changesets repository={repository} />
|
||||||
|
</BranchChooser>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
exact
|
path={`${url}/branches/:branch/changesets/:page?`}
|
||||||
path={`${url}/changesets/:page`}
|
component={() => (
|
||||||
render={() => <Changesets repository={repository} />}
|
<BranchChooser
|
||||||
/>
|
repository={repository}
|
||||||
<Route
|
label={"Branches"}
|
||||||
exact
|
branchSelected={this.branchSelected}
|
||||||
path={`${url}/:branch/changesets`}
|
>
|
||||||
render={() => <Changesets repository={repository} />}
|
<Changesets repository={repository} />
|
||||||
/>
|
</BranchChooser>
|
||||||
<Route
|
)}
|
||||||
exact
|
|
||||||
path={`${url}/:branch/changesets/:page`}
|
|
||||||
render={() => <Changesets repository={repository} />}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="column">
|
<div className="column">
|
||||||
@@ -135,7 +153,7 @@ class RepositoryRoot extends React.Component<Props> {
|
|||||||
<NavLink to={url} label={t("repository-root.information")} />
|
<NavLink to={url} label={t("repository-root.information")} />
|
||||||
<NavLink
|
<NavLink
|
||||||
activeOnlyWhenExact={false}
|
activeOnlyWhenExact={false}
|
||||||
to={`${url}/changesets`}
|
to={`${url}/changesets/`}
|
||||||
label={t("repository-root.history")}
|
label={t("repository-root.history")}
|
||||||
activeWhenMatch={this.matches}
|
activeWhenMatch={this.matches}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import {
|
|||||||
SUCCESS_SUFFIX
|
SUCCESS_SUFFIX
|
||||||
} from "../../modules/types";
|
} from "../../modules/types";
|
||||||
import { apiClient } from "@scm-manager/ui-components";
|
import { apiClient } from "@scm-manager/ui-components";
|
||||||
import type { Repository, Action, Branch } from "@scm-manager/ui-types";
|
import type { Action, Branch, Repository } from "@scm-manager/ui-types";
|
||||||
import { isPending } from "../../modules/pending";
|
import { isPending } from "../../modules/pending";
|
||||||
|
import { getFailure } from "../../modules/failure";
|
||||||
|
|
||||||
export const FETCH_BRANCHES = "scm/repos/FETCH_BRANCHES";
|
export const FETCH_BRANCHES = "scm/repos/FETCH_BRANCHES";
|
||||||
export const FETCH_BRANCHES_PENDING = `${FETCH_BRANCHES}_${PENDING_SUFFIX}`;
|
export const FETCH_BRANCHES_PENDING = `${FETCH_BRANCHES}_${PENDING_SUFFIX}`;
|
||||||
@@ -113,7 +114,7 @@ export function getBranchNames(
|
|||||||
repository: Repository
|
repository: Repository
|
||||||
): ?Array<Branch> {
|
): ?Array<Branch> {
|
||||||
const key = createKey(repository);
|
const key = createKey(repository);
|
||||||
if (!state.branches[key] || !state.branches[key].byNames) {
|
if (!state.branches || !state.branches[key] || !state.branches[key].byNames) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return Object.keys(state.branches[key].byNames);
|
return Object.keys(state.branches[key].byNames);
|
||||||
@@ -149,6 +150,10 @@ export function isFetchBranchesPending(
|
|||||||
return isPending(state, FETCH_BRANCHES, createKey(repository));
|
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 {
|
function createKey(repository: Repository): string {
|
||||||
const { namespace, name } = repository;
|
const { namespace, name } = repository;
|
||||||
return `${namespace}/${name}`;
|
return `${namespace}/${name}`;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import reducer, {
|
|||||||
getBranch,
|
getBranch,
|
||||||
getBranches,
|
getBranches,
|
||||||
getBranchNames,
|
getBranchNames,
|
||||||
|
getFetchBranchesFailure,
|
||||||
isFetchBranchesPending
|
isFetchBranchesPending
|
||||||
} from "./branches";
|
} from "./branches";
|
||||||
|
|
||||||
@@ -129,6 +130,8 @@ describe("branches", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("branch selectors", () => {
|
describe("branch selectors", () => {
|
||||||
|
const error = new Error("Something went wrong");
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
branches: {
|
branches: {
|
||||||
[key]: {
|
[key]: {
|
||||||
@@ -173,5 +176,18 @@ describe("branches", () => {
|
|||||||
const branch = getBranch(state, repository, "branch42");
|
const branch = getBranch(state, repository, "branch42");
|
||||||
expect(branch).toBeFalsy();
|
expect(branch).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
it("should return error if fetching branches failed", () => {
|
||||||
|
const state = {
|
||||||
|
failure: {
|
||||||
|
[FETCH_BRANCHES + "/foo/bar"]: error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getFetchBranchesFailure(state, repository)).toEqual(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false if fetching branches did not fail", () => {
|
||||||
|
expect(getFetchBranchesFailure({}, repository)).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,21 +1,11 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import {
|
import {FAILURE_SUFFIX, PENDING_SUFFIX, SUCCESS_SUFFIX} from "../../modules/types";
|
||||||
FAILURE_SUFFIX,
|
import {apiClient} from "@scm-manager/ui-components";
|
||||||
PENDING_SUFFIX,
|
import {isPending} from "../../modules/pending";
|
||||||
SUCCESS_SUFFIX
|
import {getFailure} from "../../modules/failure";
|
||||||
} from "../../modules/types";
|
import {combineReducers} from "redux";
|
||||||
import { apiClient } from "@scm-manager/ui-components";
|
import type {Action, Branch, Changeset, PagedCollection, Repository} from "@scm-manager/ui-types";
|
||||||
import { isPending } from "../../modules/pending";
|
|
||||||
import { getFailure } from "../../modules/failure";
|
|
||||||
import { combineReducers } from "redux";
|
|
||||||
import type {
|
|
||||||
Action,
|
|
||||||
Changeset,
|
|
||||||
PagedCollection,
|
|
||||||
Repository,
|
|
||||||
Branch
|
|
||||||
} from "@scm-manager/ui-types";
|
|
||||||
|
|
||||||
export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS";
|
export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS";
|
||||||
export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`;
|
export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`;
|
||||||
@@ -166,7 +156,7 @@ function byKeyReducer(
|
|||||||
if (state[key]) {
|
if (state[key]) {
|
||||||
oldChangesets[key] = state[key];
|
oldChangesets[key] = state[key];
|
||||||
}
|
}
|
||||||
const byIds = extractChangesetsByIds(changesets, oldChangesets[key].byId);
|
const byIds = extractChangesetsByIds(changesets);
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
[key]: {
|
[key]: {
|
||||||
@@ -190,17 +180,13 @@ export default combineReducers({
|
|||||||
byKey: byKeyReducer
|
byKey: byKeyReducer
|
||||||
});
|
});
|
||||||
|
|
||||||
function extractChangesetsByIds(changesets: any, oldChangesetsByIds: any) {
|
function extractChangesetsByIds(changesets: any) {
|
||||||
const changesetsByIds = {};
|
const changesetsByIds = {};
|
||||||
|
|
||||||
for (let changeset of changesets) {
|
for (let changeset of changesets) {
|
||||||
changesetsByIds[changeset.id] = changeset;
|
changesetsByIds[changeset.id] = changeset;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let id in oldChangesetsByIds) {
|
|
||||||
changesetsByIds[id] = oldChangesetsByIds[id];
|
|
||||||
}
|
|
||||||
|
|
||||||
return changesetsByIds;
|
return changesetsByIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,16 +219,20 @@ export function getFetchChangesetsFailure(
|
|||||||
return getFailure(state, FETCH_CHANGESETS, createItemId(repository, branch));
|
return getFailure(state, FETCH_CHANGESETS, createItemId(repository, branch));
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectList = (state: Object, repository: Repository) => {
|
const selectList = (state: Object, repository: Repository, branch?: Branch) => {
|
||||||
const itemId = createItemId(repository);
|
const itemId = createItemId(repository, branch);
|
||||||
if (state.changesets.byKey[itemId] && state.changesets.byKey[itemId].list) {
|
if (state.changesets.byKey[itemId] && state.changesets.byKey[itemId].list) {
|
||||||
return state.changesets.byKey[itemId].list;
|
return state.changesets.byKey[itemId].list;
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectListEntry = (state: Object, repository: Repository): Object => {
|
const selectListEntry = (
|
||||||
const list = selectList(state, repository);
|
state: Object,
|
||||||
|
repository: Repository,
|
||||||
|
branch?: Branch
|
||||||
|
): Object => {
|
||||||
|
const list = selectList(state, repository, branch);
|
||||||
if (list.entry) {
|
if (list.entry) {
|
||||||
return list.entry;
|
return list.entry;
|
||||||
}
|
}
|
||||||
@@ -251,9 +241,10 @@ const selectListEntry = (state: Object, repository: Repository): Object => {
|
|||||||
|
|
||||||
export const selectListAsCollection = (
|
export const selectListAsCollection = (
|
||||||
state: Object,
|
state: Object,
|
||||||
repository: Repository
|
repository: Repository,
|
||||||
|
branch?: Branch
|
||||||
): PagedCollection => {
|
): PagedCollection => {
|
||||||
return selectListEntry(state, repository);
|
return selectListEntry(state, repository, branch);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getChangesetsFromState(state: Object, repository: Repository) {
|
export function getChangesetsFromState(state: Object, repository: Repository) {
|
||||||
|
|||||||
@@ -3,21 +3,21 @@
|
|||||||
import configureMockStore from "redux-mock-store";
|
import configureMockStore from "redux-mock-store";
|
||||||
import thunk from "redux-thunk";
|
import thunk from "redux-thunk";
|
||||||
import fetchMock from "fetch-mock";
|
import fetchMock from "fetch-mock";
|
||||||
import {
|
import reducer, {
|
||||||
FETCH_CHANGESETS,
|
FETCH_CHANGESETS,
|
||||||
FETCH_CHANGESETS_FAILURE,
|
FETCH_CHANGESETS_FAILURE,
|
||||||
FETCH_CHANGESETS_PENDING,
|
FETCH_CHANGESETS_PENDING,
|
||||||
FETCH_CHANGESETS_SUCCESS,
|
FETCH_CHANGESETS_SUCCESS,
|
||||||
fetchChangesets,
|
fetchChangesets,
|
||||||
fetchChangesetsByBranchAndPage,
|
|
||||||
fetchChangesetsByBranch,
|
fetchChangesetsByBranch,
|
||||||
|
fetchChangesetsByBranchAndPage,
|
||||||
fetchChangesetsByPage,
|
fetchChangesetsByPage,
|
||||||
fetchChangesetsSuccess,
|
fetchChangesetsSuccess,
|
||||||
getChangesets,
|
getChangesets,
|
||||||
getFetchChangesetsFailure,
|
getFetchChangesetsFailure,
|
||||||
isFetchChangesetsPending
|
isFetchChangesetsPending
|
||||||
} from "./changesets";
|
} from "./changesets";
|
||||||
import reducer from "./changesets";
|
|
||||||
const branch = {
|
const branch = {
|
||||||
name: "specific",
|
name: "specific",
|
||||||
revision: "123",
|
revision: "123",
|
||||||
@@ -40,7 +40,10 @@ const repository = {
|
|||||||
changesets: {
|
changesets: {
|
||||||
href: "http://scm/api/rest/v2/repositories/foo/bar/changesets"
|
href: "http://scm/api/rest/v2/repositories/foo/bar/changesets"
|
||||||
},
|
},
|
||||||
branches: [branch]
|
branches: {
|
||||||
|
href:
|
||||||
|
"http://scm/api/rest/v2/repositories/foo/bar/branches/specific/branches"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -238,38 +241,6 @@ describe("changesets", () => {
|
|||||||
entries: ["changeset1", "changeset2", "changeset3"]
|
entries: ["changeset1", "changeset2", "changeset3"]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not delete existing changesets from state", () => {
|
|
||||||
const responseBody = {
|
|
||||||
_embedded: {
|
|
||||||
changesets: [
|
|
||||||
{ id: "changeset1", author: { mail: "z@phod.com", name: "zaphod" } }
|
|
||||||
],
|
|
||||||
_embedded: {
|
|
||||||
tags: [],
|
|
||||||
branches: [],
|
|
||||||
parents: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const newState = reducer(
|
|
||||||
{
|
|
||||||
byKey: {
|
|
||||||
"foo/bar": {
|
|
||||||
byId: {
|
|
||||||
["changeset2"]: {
|
|
||||||
id: "changeset2",
|
|
||||||
author: { mail: "mail@author.com", name: "author" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fetchChangesetsSuccess(responseBody, repository)
|
|
||||||
);
|
|
||||||
expect(newState.byKey["foo/bar"].byId["changeset2"]).toBeDefined();
|
|
||||||
expect(newState.byKey["foo/bar"].byId["changeset1"]).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("changeset selectors", () => {
|
describe("changeset selectors", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user