From 57a4e42da6220848286278342ee6787a20bcad73 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Wed, 19 Sep 2018 17:18:24 +0200 Subject: [PATCH 01/52] Implemented paging WIP --- scm-ui/src/repos/containers/Changesets.js | 58 +++++++----- scm-ui/src/repos/modules/changesets.js | 93 ++++++++++++------- scm-ui/src/repos/modules/changesets.test.js | 2 +- .../sonia/scm/api/v2/resources/GroupDto.java | 1 - 4 files changed, 96 insertions(+), 58 deletions(-) diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index 21c8211d90..50bb0041c2 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -10,10 +10,11 @@ import { import { fetchChangesets, fetchChangesetsByNamespaceNameAndBranch, - getChangesets, getFetchChangesetsFailure, isFetchChangesetsPending, - selectListAsCollection + selectListAsCollection, + fetchChangesetsByLink, + getChangesetsFromState } from "../modules/changesets"; import type { History } from "history"; import { @@ -34,7 +35,9 @@ type Props = { name: string, branch: string ) => void, - list: PagedCollection + list: PagedCollection, + fetchChangesetsByLink: string => void, + page: number }; class Changesets extends React.Component { @@ -44,7 +47,11 @@ class Changesets extends React.Component { } onPageChange = (link: string) => { + const { namespace, name } = this.props.repository; + const branch = this.props.match.params.branch; + this.props.fetchChangesetsByLink(namespace, name, link, branch); }; + componentDidMount() { const { namespace, name } = this.props.repository; const branchName = this.props.match.params.branch; @@ -112,28 +119,27 @@ class Changesets extends React.Component { }; } +const createKey = ( + namespace: string, + name: string, + branch?: string +): string => { + let key = `${namespace}/${name}`; + if (branch) { + key = key + `/${branch}`; + } + return key; +}; + const mapStateToProps = (state, ownProps: Props) => { const { namespace, name } = ownProps.repository; - const loading = isFetchChangesetsPending( - state, - namespace, - name, - ownProps.match.params.branch - ); - const changesets = getChangesets( - state, - namespace, - name, - ownProps.match.params.branch - ); + const { branch } = ownProps.match.params; + const key = createKey(namespace, name, branch); + const loading = isFetchChangesetsPending(state, namespace, name, branch); + const changesets = getChangesetsFromState(state, key); const branchNames = getBranchNames(namespace, name, state); - const error = getFetchChangesetsFailure( - state, - namespace, - name, - ownProps.match.params.branch - ); - const list = selectListAsCollection(state); + const error = getFetchChangesetsFailure(state, namespace, name, branch); + const list = selectListAsCollection(state, key); return { loading, @@ -160,6 +166,14 @@ const mapDispatchToProps = dispatch => { }, fetchBranchesByNamespaceAndName: (namespace: string, name: string) => { dispatch(fetchBranchesByNamespaceAndName(namespace, name)); + }, + fetchChangesetsByLink: ( + namespace: string, + name: string, + link: string, + branch?: string + ) => { + dispatch(fetchChangesetsByLink(namespace, name, link, branch)); } }; }; diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index b806fff535..658821534b 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -9,7 +9,8 @@ import { apiClient } from "@scm-manager/ui-components"; import { isPending } from "../../modules/pending"; import { getFailure } from "../../modules/failure"; import { combineReducers } from "redux"; -import type { Action, PagedCollection } from "@scm-manager/ui-types"; +import type { Action, Changeset, PagedCollection } from "@scm-manager/ui-types"; +import ChangesetAvatar from "../components/ChangesetAvatar"; export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS"; export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; @@ -20,6 +21,26 @@ const REPO_URL = "repositories"; //TODO: Content type // actions +export function fetchChangesetsByLink( + namespace: string, + name: string, + link: string, + branch?: string +) { + return function(dispatch: any) { + // dispatch(fetchChangesetsPending(namespace, name, branch)); + return apiClient + .get(link) + .then(response => response.json()) + .then(data => { + dispatch(fetchChangesetsSuccess(data, namespace, name, branch)); + }) + .catch(cause => { + dispatch(fetchChangesetsFailure(namespace, name, cause, branch)); + }); + }; +} + export function fetchChangesetsWithOptions( namespace: string, name: string, @@ -140,36 +161,25 @@ function byKeyReducer( ): Object { switch (action.type) { case FETCH_CHANGESETS_SUCCESS: + const changesets = action.payload._embedded.changesets; + const changesetIds = changesets.map(c => c.id); const key = action.itemId; let oldChangesets = { [key]: {} }; if (state[key] !== undefined) { oldChangesets[key] = state[key]; } + const byIds = extractChangesetsByIds(changesets, oldChangesets[key].byId); return { - ...state, [key]: { - byId: extractChangesetsByIds(action.payload, oldChangesets[key].byId) - } - }; - default: - return state; - } -} - -function listReducer( - state: any = {}, - action: Action = { type: "UNKNOWN" } -): Object { - switch (action.type) { - case FETCH_CHANGESETS_SUCCESS: - const changesets = action.payload._embedded.changesets; - const changesetIds = changesets.map(c => c.id); - return { - entries: changesetIds, - entry: { - page: action.payload.page, - pageTotal: action.payload.pageTotal, - _links: action.payload._links + byId: { ...byIds }, + list: { + entries: changesetIds, + entry: { + page: action.payload.page, + pageTotal: action.payload.pageTotal, + _links: action.payload._links + } + } } }; default: @@ -178,12 +188,10 @@ function listReducer( } export default combineReducers({ - list: listReducer, byKey: byKeyReducer }); -function extractChangesetsByIds(data: any, oldChangesetsByIds: any) { - const changesets = data._embedded.changesets; +function extractChangesetsByIds(changesets: any, oldChangesetsByIds: any) { const changesetsByIds = {}; for (let changeset of changesets) { @@ -237,21 +245,38 @@ export function getFetchChangesetsFailure( ); } -const selectList = (state: Object) => { - if (state.changesets && state.changesets.list) { - return state.changesets.list; +const selectList = (state: Object, key: string) => { + if (state.changesets.byKey[key] && state.changesets.byKey[key].list) { + return state.changesets.byKey[key].list; } return {}; }; -const selectListEntry = (state: Object): Object => { - const list = selectList(state); +const selectListEntry = (state: Object, key: string): Object => { + const list = selectList(state, key); if (list.entry) { return list.entry; } return {}; }; -export const selectListAsCollection = (state: Object): PagedCollection => { - return selectListEntry(state); +export const selectListAsCollection = ( + state: Object, + key: string +): PagedCollection => { + return selectListEntry(state, key); }; + +export function getChangesetsFromState(state: Object, key: string) { + const changesetIds = selectList(state, key).entries; + if (!changesetIds) { + return null; + } + const changesetEntries: Changeset[] = []; + + for (let id of changesetIds) { + changesetEntries.push(state.changesets.byKey[key].byId[id]); + } + + return changesetEntries; +} diff --git a/scm-ui/src/repos/modules/changesets.test.js b/scm-ui/src/repos/modules/changesets.test.js index d46dff8ef7..7122ed3772 100644 --- a/scm-ui/src/repos/modules/changesets.test.js +++ b/scm-ui/src/repos/modules/changesets.test.js @@ -207,7 +207,7 @@ describe("changesets", () => { expect(newState.byKey["foo/bar"].byId["changeset3"].description).toEqual( "bar" ); - expect(newState.list).toEqual({ + expect(newState.byKey["foo/bar"].list).toEqual({ entry: { page: 1, pageTotal: 10, diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java index b847412a33..2577b46c4e 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java @@ -22,7 +22,6 @@ public class GroupDto extends HalRepresentation { private Instant lastModified; @Pattern(regexp = "^[A-z0-9\\.\\-_@]|[^ ]([A-z0-9\\.\\-_@ ]*[A-z0-9\\.\\-_@]|[^ ])?$") private String name; - @NotEmpty private String type; private Map properties; private List members; From 8fefe5ff741767fe4c3806bbaf16782e62d30912 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Thu, 20 Sep 2018 16:28:41 +0200 Subject: [PATCH 02/52] page numbers for Changesets are now present in the URL --- .../src/repos/components/ChangesetAvatar.js | 6 +- scm-ui/src/repos/containers/Changesets.js | 91 ++++++++++++++----- scm-ui/src/repos/containers/RepositoryRoot.js | 52 +++++++---- scm-ui/src/repos/modules/changesets.js | 13 ++- 4 files changed, 112 insertions(+), 50 deletions(-) diff --git a/scm-ui/src/repos/components/ChangesetAvatar.js b/scm-ui/src/repos/components/ChangesetAvatar.js index eb461d374d..f7d6ab21af 100644 --- a/scm-ui/src/repos/components/ChangesetAvatar.js +++ b/scm-ui/src/repos/components/ChangesetAvatar.js @@ -1,8 +1,8 @@ //@flow import React from "react"; -import {ExtensionPoint} from "@scm-manager/ui-extensions"; -import type {Changeset} from "@scm-manager/ui-types"; -import {Image} from "@scm-manager/ui-components"; +import { ExtensionPoint } from "@scm-manager/ui-extensions"; +import type { Changeset } from "@scm-manager/ui-types"; +import { Image } from "@scm-manager/ui-components"; type Props = { changeset: Changeset diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index 48a2f0d476..a3aa093907 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -4,17 +4,18 @@ import { connect } from "react-redux"; import { ErrorNotification, Loading, + Page, Paginator } from "@scm-manager/ui-components"; import { - fetchChangesets, - fetchChangesetsByNamespaceNameAndBranch, getFetchChangesetsFailure, isFetchChangesetsPending, selectListAsCollection, fetchChangesetsByLink, - getChangesetsFromState + getChangesetsFromState, + fetchChangesetsByPage, + fetchChangesetsByBranchAndPage } from "../modules/changesets"; import type { History } from "history"; import { @@ -40,7 +41,7 @@ type Props = { page: number }; -class Changesets extends React.Component { +class Changesets extends React.PureComponent { constructor(props) { super(props); this.state = {}; @@ -56,26 +57,57 @@ class Changesets extends React.Component { const { namespace, name } = this.props.repository; const branchName = this.props.match.params.branch; const { - fetchChangesetsByNamespaceNameAndBranch, - fetchChangesetsByNamespaceAndName, - fetchBranchesByNamespaceAndName + fetchBranchesByNamespaceAndName, + fetchChangesetsByPage, + fetchChangesetsByBranchAndPage } = this.props; if (branchName) { - fetchChangesetsByNamespaceNameAndBranch(namespace, name, branchName); + fetchChangesetsByBranchAndPage( + namespace, + name, + branchName, + this.props.page + ); } else { - fetchChangesetsByNamespaceAndName(namespace, name); + fetchChangesetsByPage(namespace, name, this.props.page); } fetchBranchesByNamespaceAndName(namespace, name); } + componentDidUpdate() { + const { page, list, repository, match } = this.props; + const { namespace, name } = repository; + const branch = match.params.branch; + + if (list && (list.page || list.page === 0)) { + // backend starts paging at 0 + const statePage: number = list.page + 1; + if (page !== statePage) { + if (branch) { + this.props.history.push( + `/repo/${namespace}/${name}/${branch}/history/${statePage}` + ); + } else { + this.props.history.push( + `/repo/${namespace}/${name}/history/${statePage}` + ); + } + } + } + } + render() { const { changesets, loading, error } = this.props; + if (loading || !changesets) { return ; } + + if (error) { + return ; + } return (
- {this.renderTable()} {this.renderPaginator()}
@@ -114,7 +146,7 @@ class Changesets extends React.Component { branchChanged = (branchName: string): void => { const { history, repository } = this.props; history.push( - `/repo/${repository.namespace}/${repository.name}/history/${branchName}` + `/repo/${repository.namespace}/${repository.name}/${branchName}/history` ); }; } @@ -131,6 +163,16 @@ const createKey = ( return key; }; +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 { namespace, name } = ownProps.repository; const { branch } = ownProps.match.params; @@ -140,33 +182,34 @@ const mapStateToProps = (state, ownProps: Props) => { const branchNames = getBranchNames(namespace, name, state); const error = getFetchChangesetsFailure(state, namespace, name, branch); const list = selectListAsCollection(state, key); + const page = getPageFromProps(ownProps); return { loading, changesets, branchNames, error, - list + list, + page }; }; const mapDispatchToProps = dispatch => { return { - fetchChangesetsByNamespaceAndName: (namespace: string, name: string) => { - dispatch(fetchChangesets(namespace, name)); - }, - fetchChangesetsByNamespaceNameAndBranch: ( - namespace: string, - name: string, - branch: string - ) => { - dispatch( - fetchChangesetsByNamespaceNameAndBranch(namespace, name, branch) - ); - }, fetchBranchesByNamespaceAndName: (namespace: string, name: string) => { dispatch(fetchBranchesByNamespaceAndName(namespace, name)); }, + fetchChangesetsByPage: (namespace: string, name: string, page: number) => { + dispatch(fetchChangesetsByPage(namespace, name, page)); + }, + fetchChangesetsByBranchAndPage: ( + namespace: string, + name: string, + branch: string, + page: number + ) => { + dispatch(fetchChangesetsByBranchAndPage(namespace, name, branch, page)); + }, fetchChangesetsByLink: ( namespace: string, name: string, diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index e1352fba31..1cb7521e24 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -7,9 +7,9 @@ import { getRepository, isFetchRepoPending } from "../modules/repos"; -import {connect} from "react-redux"; -import {Route} from "react-router-dom"; -import type {Repository} from "@scm-manager/ui-types"; +import { connect } from "react-redux"; +import { Route } from "react-router-dom"; +import type { Repository } from "@scm-manager/ui-types"; import { Page, Loading, @@ -18,12 +18,12 @@ import { NavLink, Section } from "@scm-manager/ui-components"; -import {translate} from "react-i18next"; +import { translate } from "react-i18next"; import RepositoryDetails from "../components/RepositoryDetails"; import DeleteNavAction from "../components/DeleteNavAction"; import Edit from "../containers/Edit"; -import type {History} from "history"; +import type { History } from "history"; import EditNavLink from "../components/EditNavLink"; import Changesets from "./Changesets"; @@ -46,7 +46,7 @@ type Props = { class RepositoryRoot extends React.Component { componentDidMount() { - const {fetchRepo, namespace, name} = this.props; + const { fetchRepo, namespace, name } = this.props; fetchRepo(namespace, name); } @@ -71,7 +71,7 @@ class RepositoryRoot extends React.Component { }; render() { - const {loading, error, repository, t} = this.props; + const { loading, error, repository, t } = this.props; if (error) { return ( @@ -84,7 +84,7 @@ class RepositoryRoot extends React.Component { } if (!repository || loading) { - return ; + return ; } const url = this.matchedUrl(); @@ -96,33 +96,47 @@ class RepositoryRoot extends React.Component { } + component={() => } /> } + component={() => } /> } + component={() => } /> } + path={`${url}/history/:page`} + component={() => } + /> + } + /> + } />
- - - + + +
- - + +
@@ -133,7 +147,7 @@ class RepositoryRoot extends React.Component { } const mapStateToProps = (state, ownProps) => { - const {namespace, name} = ownProps.match.params; + const { namespace, name } = ownProps.match.params; const repository = getRepository(state, namespace, name); const loading = isFetchRepoPending(state, namespace, name); const error = getFetchRepoFailure(state, namespace, name); diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index 658821534b..0ab85183a8 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -10,7 +10,6 @@ import { isPending } from "../../modules/pending"; import { getFailure } from "../../modules/failure"; import { combineReducers } from "redux"; import type { Action, Changeset, PagedCollection } from "@scm-manager/ui-types"; -import ChangesetAvatar from "../components/ChangesetAvatar"; export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS"; export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; @@ -28,7 +27,7 @@ export function fetchChangesetsByLink( branch?: string ) { return function(dispatch: any) { - // dispatch(fetchChangesetsPending(namespace, name, branch)); + dispatch(fetchChangesetsPending(namespace, name, branch)); return apiClient .get(link) .then(response => response.json()) @@ -55,6 +54,7 @@ export function fetchChangesetsWithOptions( if (suffix) { link = link + `${suffix}`; } + return function(dispatch: any) { dispatch(fetchChangesetsPending(namespace, name, branch)); return apiClient @@ -78,7 +78,7 @@ export function fetchChangesetsByPage( name: string, page: number ) { - return fetchChangesetsWithOptions(namespace, name, "", `?page=${page}`); + return fetchChangesetsWithOptions(namespace, name, "", `?page=${page - 1}`); } export function fetchChangesetsByBranchAndPage( @@ -87,7 +87,12 @@ export function fetchChangesetsByBranchAndPage( branch: string, page: number ) { - return fetchChangesetsWithOptions(namespace, name, branch, `?page=${page}`); + return fetchChangesetsWithOptions( + namespace, + name, + branch, + `?page=${page - 1}` + ); } export function fetchChangesetsByNamespaceNameAndBranch( From 360bb807b91347b78c02044465e60ced14327436 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Thu, 27 Sep 2018 16:29:33 +0200 Subject: [PATCH 03/52] Fixed bug causing multiple requests for changesets Fixed bug related to displaying errors additionally: implemented i18n --- scm-ui/public/locales/en/changesets.json | 13 ---- scm-ui/public/locales/en/repos.json | 14 ++++ .../{ => changesets}/ChangesetAuthor.js | 4 +- .../{ => changesets}/ChangesetAvatar.js | 8 +- .../{ => changesets}/ChangesetId.js | 0 .../{ => changesets}/ChangesetList.js | 2 +- .../{ => changesets}/ChangesetRow.js | 13 ++-- scm-ui/src/repos/containers/Changesets.js | 47 ++++++++---- scm-ui/src/repos/containers/RepositoryRoot.js | 8 +- scm-ui/src/repos/modules/branches.js | 76 +++++++++++++------ scm-ui/src/repos/modules/changesets.js | 4 +- scm-ui/src/repos/modules/changesets.test.js | 4 +- 12 files changed, 122 insertions(+), 71 deletions(-) delete mode 100644 scm-ui/public/locales/en/changesets.json rename scm-ui/src/repos/components/{ => changesets}/ChangesetAuthor.js (95%) rename scm-ui/src/repos/components/{ => changesets}/ChangesetAvatar.js (68%) rename scm-ui/src/repos/components/{ => changesets}/ChangesetId.js (100%) rename scm-ui/src/repos/components/{ => changesets}/ChangesetList.js (83%) rename scm-ui/src/repos/components/{ => changesets}/ChangesetRow.js (80%) diff --git a/scm-ui/public/locales/en/changesets.json b/scm-ui/public/locales/en/changesets.json deleted file mode 100644 index b3e996e094..0000000000 --- a/scm-ui/public/locales/en/changesets.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "changeset": { - "id": "ID", - "description": "Description", - "contact": "Contact", - "date": "Date", - "summary": "Changeset {{id}} committed {{time}}" - }, - "author": { - "name": "Author", - "mail": "Mail" - } -} diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 678a7a07b4..67732372a3 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -43,5 +43,19 @@ "submit": "Yes", "cancel": "No" } + }, + "changesets": { + "changeset": { + "id": "ID", + "description": "Description", + "contact": "Contact", + "date": "Date", + "summary": "Changeset {{id}} committed {{time}}" + }, + "author": { + "name": "Author", + "mail": "Mail" + }, + "branchselector-label": "Branch:" } } diff --git a/scm-ui/src/repos/components/ChangesetAuthor.js b/scm-ui/src/repos/components/changesets/ChangesetAuthor.js similarity index 95% rename from scm-ui/src/repos/components/ChangesetAuthor.js rename to scm-ui/src/repos/components/changesets/ChangesetAuthor.js index d2993daed4..d90b6674b7 100644 --- a/scm-ui/src/repos/components/ChangesetAuthor.js +++ b/scm-ui/src/repos/components/changesets/ChangesetAuthor.js @@ -11,7 +11,7 @@ export default class ChangesetAuthor extends React.Component { render() { const { changeset } = this.props; return ( - <> +
{changeset.author.name}{" "} { {changeset.author.mail} > - +
); } } diff --git a/scm-ui/src/repos/components/ChangesetAvatar.js b/scm-ui/src/repos/components/changesets/ChangesetAvatar.js similarity index 68% rename from scm-ui/src/repos/components/ChangesetAvatar.js rename to scm-ui/src/repos/components/changesets/ChangesetAvatar.js index f7d6ab21af..dc12405590 100644 --- a/scm-ui/src/repos/components/ChangesetAvatar.js +++ b/scm-ui/src/repos/components/changesets/ChangesetAvatar.js @@ -1,8 +1,8 @@ //@flow import React from "react"; import { ExtensionPoint } from "@scm-manager/ui-extensions"; -import type { Changeset } from "@scm-manager/ui-types"; -import { Image } from "@scm-manager/ui-components"; +import type { Changeset } from "../../../../../scm-ui-components/packages/ui-types/src/index"; +import { Image } from "../../../../../scm-ui-components/packages/ui-components/src/index"; type Props = { changeset: Changeset @@ -12,7 +12,7 @@ class ChangesetAvatar extends React.Component { render() { const { changeset } = this.props; return ( -

+

{ > Logo -

+
); } } diff --git a/scm-ui/src/repos/components/ChangesetId.js b/scm-ui/src/repos/components/changesets/ChangesetId.js similarity index 100% rename from scm-ui/src/repos/components/ChangesetId.js rename to scm-ui/src/repos/components/changesets/ChangesetId.js diff --git a/scm-ui/src/repos/components/ChangesetList.js b/scm-ui/src/repos/components/changesets/ChangesetList.js similarity index 83% rename from scm-ui/src/repos/components/ChangesetList.js rename to scm-ui/src/repos/components/changesets/ChangesetList.js index b5613b3754..4dd1762e1c 100644 --- a/scm-ui/src/repos/components/ChangesetList.js +++ b/scm-ui/src/repos/components/changesets/ChangesetList.js @@ -1,7 +1,7 @@ // @flow import ChangesetRow from "./ChangesetRow"; import React from "react"; -import type { Changeset, Repository } from "@scm-manager/ui-types"; +import type { Changeset, Repository } from "../../../../../scm-ui-components/packages/ui-types/src/index"; import classNames from "classnames"; type Props = { diff --git a/scm-ui/src/repos/components/ChangesetRow.js b/scm-ui/src/repos/components/changesets/ChangesetRow.js similarity index 80% rename from scm-ui/src/repos/components/ChangesetRow.js rename to scm-ui/src/repos/components/changesets/ChangesetRow.js index 9ec3a7af58..645f3b8129 100644 --- a/scm-ui/src/repos/components/ChangesetRow.js +++ b/scm-ui/src/repos/components/changesets/ChangesetRow.js @@ -1,12 +1,15 @@ //@flow import React from "react"; -import type { Changeset, Repository } from "@scm-manager/ui-types"; +import type { + Changeset, + Repository +} from "../../../../../scm-ui-components/packages/ui-types/src/index"; import classNames from "classnames"; import { translate, Interpolate } from "react-i18next"; import ChangesetAvatar from "./ChangesetAvatar"; import ChangesetId from "./ChangesetId"; import injectSheet from "react-jss"; -import { DateFromNow } from "@scm-manager/ui-components"; +import { DateFromNow } from "../../../../../scm-ui-components/packages/ui-components/src/index"; import ChangesetAuthor from "./ChangesetAuthor"; const styles = { @@ -38,7 +41,7 @@ class ChangesetRow extends React.Component { const { changeset, classes } = this.props; const changesetLink = this.createLink(changeset); const dateFromNow = ; - const authorLine = ; + const authorLine = ; return (
@@ -50,7 +53,7 @@ class ChangesetRow extends React.Component { {changeset.description}
@@ -63,4 +66,4 @@ class ChangesetRow extends React.Component { } } -export default injectSheet(styles)(translate("changesets")(ChangesetRow)); +export default injectSheet(styles)(translate("repos")(ChangesetRow)); diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index a3aa093907..7e22e50055 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -1,10 +1,10 @@ // @flow import React from "react"; import { connect } from "react-redux"; +import { translate } from "react-i18next"; import { ErrorNotification, Loading, - Page, Paginator } from "@scm-manager/ui-components"; @@ -23,7 +23,7 @@ import { getBranchNames } from "../../repos/modules/branches"; import type { PagedCollection, Repository } from "@scm-manager/ui-types"; -import ChangesetList from "../components/ChangesetList"; +import ChangesetList from "../components/changesets/ChangesetList"; import DropDown from "../components/DropDown"; import { withRouter } from "react-router-dom"; @@ -38,7 +38,12 @@ type Props = { ) => void, list: PagedCollection, fetchChangesetsByLink: string => void, - page: number + page: number, + t: string => string +}; + +type State = { + branch: string }; class Changesets extends React.PureComponent { @@ -54,6 +59,10 @@ class Changesets extends React.PureComponent { }; componentDidMount() { + this.updateContent(); + } + + updateContent() { const { namespace, name } = this.props.repository; const branchName = this.props.match.params.branch; const { @@ -74,11 +83,16 @@ class Changesets extends React.PureComponent { fetchBranchesByNamespaceAndName(namespace, name); } - componentDidUpdate() { + componentDidUpdate(prevProps: Props, prevState: State, snapshot: any) { const { page, list, repository, match } = this.props; const { namespace, name } = repository; const branch = match.params.branch; + if (branch !== prevState.branch) { + this.updateContent(); + this.setState({ branch }); + } + if (list && (list.page || list.page === 0)) { // backend starts paging at 0 const statePage: number = list.page + 1; @@ -99,13 +113,14 @@ class Changesets extends React.PureComponent { render() { const { changesets, loading, error } = this.props; + if (error) { + return ; + } + if (loading || !changesets) { return ; } - if (error) { - return ; - } return (
{this.renderTable()} @@ -116,12 +131,14 @@ class Changesets extends React.PureComponent { renderTable = () => { const branch = this.props.match.params.branch; - const { repository, changesets, branchNames } = this.props; + const { repository, changesets, branchNames, t } = this.props; if (branchNames && branchNames.length > 0) { return (
- + { branchChanged = (branchName: string): void => { const { history, repository } = this.props; - history.push( - `/repo/${repository.namespace}/${repository.name}/${branchName}/history` - ); + if (branchName === undefined || branchName === "") { + history.push(`/repo/${repository.namespace}/${repository.name}/history`); + } else { + history.push( + `/repo/${repository.namespace}/${repository.name}/${branchName}/history` + ); + } }; } @@ -225,5 +246,5 @@ export default withRouter( connect( mapStateToProps, mapDispatchToProps - )(Changesets) + )(translate("repos")(Changesets)) ); diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 1cb7521e24..b274540370 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -105,22 +105,22 @@ class RepositoryRoot extends React.Component { } + render={() => } /> } + render={() => } /> } + render={() => } /> } + render={() => } />
diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index 82c8940df4..1bd8d30df5 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -1,5 +1,10 @@ -import {FAILURE_SUFFIX, PENDING_SUFFIX, SUCCESS_SUFFIX} from "../../modules/types"; -import {apiClient} from "@scm-manager/ui-components"; +// @flow +import { + FAILURE_SUFFIX, + PENDING_SUFFIX, + SUCCESS_SUFFIX +} from "../../modules/types"; +import { apiClient } from "@scm-manager/ui-components"; export const FETCH_BRANCHES = "scm/repos/FETCH_BRANCHES"; export const FETCH_BRANCHES_PENDING = `${FETCH_BRANCHES}_${PENDING_SUFFIX}`; @@ -9,64 +14,81 @@ export const FETCH_BRANCHES_FAILURE = `${FETCH_BRANCHES}_${FAILURE_SUFFIX}`; const REPO_URL = "repositories"; // Fetching branches -export function fetchBranchesByNamespaceAndName(namespace: string, name: string) { - return function (dispatch: any) { +export function fetchBranchesByNamespaceAndName( + namespace: string, + name: string +) { + return function(dispatch: any) { dispatch(fetchBranchesPending(namespace, name)); - return apiClient.get(REPO_URL + "/" + namespace + "/" + name + "/branches") + return apiClient + .get(REPO_URL + "/" + namespace + "/" + name + "/branches") .then(response => response.json()) .then(data => { - dispatch(fetchBranchesSuccess(data, namespace, name)) + dispatch(fetchBranchesSuccess(data, namespace, name)); }) - .catch(cause => { - dispatch(fetchBranchesFailure(namespace, name, cause)) - }) - } + .catch(error => { + dispatch(fetchBranchesFailure(namespace, name, error)); + }); + }; } // Action creators export function fetchBranchesPending(namespace: string, name: string) { return { type: FETCH_BRANCHES_PENDING, - payload: {namespace, name}, + payload: { namespace, name }, itemId: namespace + "/" + name - } + }; } -export function fetchBranchesSuccess(data: string, namespace: string, name: string) { +export function fetchBranchesSuccess( + data: string, + namespace: string, + name: string +) { return { type: FETCH_BRANCHES_SUCCESS, - payload: {data, namespace, name}, + payload: { data, namespace, name }, itemId: namespace + "/" + name - } + }; } -export function fetchBranchesFailure(namespace: string, name: string, error: Error) { +export function fetchBranchesFailure( + namespace: string, + name: string, + error: Error +) { return { type: FETCH_BRANCHES_FAILURE, - payload: {error, namespace, name}, + payload: { error, namespace, name }, itemId: namespace + "/" + name - } + }; } // Reducers -export default function reducer(state: Object = {}, action: Action = {type: "UNKNOWN"}): Object { +export default function reducer( + state: Object = {}, + action: Action = { type: "UNKNOWN" } +): Object { switch (action.type) { case FETCH_BRANCHES_SUCCESS: const key = action.payload.namespace + "/" + action.payload.name; - let oldBranchesByNames = {[key]: {}}; + let oldBranchesByNames = { [key]: {} }; if (state[key] !== undefined) { - oldBranchesByNames[key] = state[key] + oldBranchesByNames[key] = state[key]; } return { [key]: { - byNames: extractBranchesByNames(action.payload.data, oldBranchesByNames[key].byNames) + byNames: extractBranchesByNames( + action.payload.data, + oldBranchesByNames[key].byNames + ) } }; default: return state; } - } function extractBranchesByNames(data: any, oldBranchesByNames: any): Branch[] { @@ -78,14 +100,18 @@ function extractBranchesByNames(data: any, oldBranchesByNames: any): Branch[] { } for (let name in oldBranchesByNames) { - branchesByNames[name] = oldBranchesByNames[name] + branchesByNames[name] = oldBranchesByNames[name]; } return branchesByNames; } // Selectors -export function getBranchesForNamespaceAndNameFromState(namespace: string, name: string, state: Object) { +export function getBranchesForNamespaceAndNameFromState( + namespace: string, + name: string, + state: Object +) { const key = namespace + "/" + name; if (!state.branches[key]) { return null; diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index 0ab85183a8..bcc323a937 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -140,8 +140,8 @@ function fetchChangesetsFailure( payload: { namespace, name, - branch, - error + error, + branch }, itemId: createItemId(namespace, name, branch) }; diff --git a/scm-ui/src/repos/modules/changesets.test.js b/scm-ui/src/repos/modules/changesets.test.js index 7122ed3772..40f18c2423 100644 --- a/scm-ui/src/repos/modules/changesets.test.js +++ b/scm-ui/src/repos/modules/changesets.test.js @@ -127,7 +127,7 @@ describe("changesets", () => { }); it("should fetch changesets by page", () => { - fetchMock.getOnce(DEFAULT_BRANCH_URL + "?page=5", "{}"); + fetchMock.getOnce(DEFAULT_BRANCH_URL + "?page=4", "{}"); const expectedActions = [ { @@ -149,7 +149,7 @@ describe("changesets", () => { }); it("should fetch changesets by branch and page", () => { - fetchMock.getOnce(SPECIFIC_BRANCH_URL + "?page=5", "{}"); + fetchMock.getOnce(SPECIFIC_BRANCH_URL + "?page=4", "{}"); const expectedActions = [ { From 562978e6232496e02ad9f1e7ad0195e40f7917ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 4 Oct 2018 15:46:17 +0200 Subject: [PATCH 04/52] Remove scm logo from changesets --- .../components/changesets/ChangesetAvatar.js | 21 +++++++++++-------- .../components/changesets/ChangesetRow.js | 4 +--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/scm-ui/src/repos/components/changesets/ChangesetAvatar.js b/scm-ui/src/repos/components/changesets/ChangesetAvatar.js index dc12405590..0a5afc1778 100644 --- a/scm-ui/src/repos/components/changesets/ChangesetAvatar.js +++ b/scm-ui/src/repos/components/changesets/ChangesetAvatar.js @@ -12,15 +12,18 @@ class ChangesetAvatar extends React.Component { render() { const { changeset } = this.props; return ( -
- - Logo - -
+ + {/* extension should render something like this: */} + {/*
*/} + {/*
*/} + {/* Logo */} + {/*
*/} + {/*
*/} +
); } } diff --git a/scm-ui/src/repos/components/changesets/ChangesetRow.js b/scm-ui/src/repos/components/changesets/ChangesetRow.js index 645f3b8129..fca5c53d9b 100644 --- a/scm-ui/src/repos/components/changesets/ChangesetRow.js +++ b/scm-ui/src/repos/components/changesets/ChangesetRow.js @@ -44,9 +44,7 @@ class ChangesetRow extends React.Component { const authorLine = ; return (
-
- -
+

From e688da4d91c918c5322c78f951284b178aa84d62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 4 Oct 2018 15:52:17 +0200 Subject: [PATCH 05/52] Add space between changeset list and paginator --- scm-ui/src/repos/containers/Changesets.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index 7e22e50055..0e0ca3f172 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -123,19 +123,19 @@ class Changesets extends React.PureComponent { return (

- {this.renderTable()} + {this.renderList()} {this.renderPaginator()}
); } - renderTable = () => { + renderList = () => { const branch = this.props.match.params.branch; const { repository, changesets, branchNames, t } = this.props; if (branchNames && branchNames.length > 0) { return ( -
+ <> @@ -145,7 +145,7 @@ class Changesets extends React.PureComponent { optionSelected={branch => this.branchChanged(branch)} /> -
+ ); } From 7019073b93243fb7d283c19f9bf875b190344fef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 4 Oct 2018 16:00:02 +0200 Subject: [PATCH 06/52] Fix wrong div in p --- scm-ui/src/repos/components/changesets/ChangesetAuthor.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-ui/src/repos/components/changesets/ChangesetAuthor.js b/scm-ui/src/repos/components/changesets/ChangesetAuthor.js index d90b6674b7..d2993daed4 100644 --- a/scm-ui/src/repos/components/changesets/ChangesetAuthor.js +++ b/scm-ui/src/repos/components/changesets/ChangesetAuthor.js @@ -11,7 +11,7 @@ export default class ChangesetAuthor extends React.Component { render() { const { changeset } = this.props; return ( -
+ <> {changeset.author.name}{" "} { {changeset.author.mail} > -
+ ); } } From 8e23a85e7c9650fa38591ead592e812e5e77b9bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 4 Oct 2018 16:09:12 +0200 Subject: [PATCH 07/52] Render branch chosser as own box --- scm-ui/src/repos/containers/Changesets.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index 0e0ca3f172..04c4ec1885 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -136,14 +136,16 @@ class Changesets extends React.PureComponent { if (branchNames && branchNames.length > 0) { return ( <> - - this.branchChanged(branch)} - /> +
+ + this.branchChanged(branch)} + /> +
); From 35bbb44aaa3e5bcacc58db181e894785da9d3bf1 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Thu, 4 Oct 2018 17:12:38 +0200 Subject: [PATCH 08/52] Started refactoring Changeset listing --- .../components/changesets/ChangesetRow.js | 2 +- scm-ui/src/repos/containers/Changesets.js | 60 ++--- scm-ui/src/repos/modules/branches.js | 62 +++-- scm-ui/src/repos/modules/branches.test.js | 241 ++++++++++-------- scm-ui/src/repos/modules/changesets.js | 121 ++++----- scm-ui/src/repos/modules/changesets.test.js | 75 ++++-- 6 files changed, 289 insertions(+), 272 deletions(-) diff --git a/scm-ui/src/repos/components/changesets/ChangesetRow.js b/scm-ui/src/repos/components/changesets/ChangesetRow.js index 645f3b8129..02a97c8da0 100644 --- a/scm-ui/src/repos/components/changesets/ChangesetRow.js +++ b/scm-ui/src/repos/components/changesets/ChangesetRow.js @@ -58,7 +58,7 @@ class ChangesetRow extends React.Component { time={dateFromNow} />

{" "} -

{authorLine}

+
{authorLine}
diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index 7e22e50055..45f72fb5a7 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -15,17 +15,15 @@ import { fetchChangesetsByLink, getChangesetsFromState, fetchChangesetsByPage, - fetchChangesetsByBranchAndPage + fetchChangesetsByBranchAndPage, + fetchChangesets } from "../modules/changesets"; import type { History } from "history"; -import { - fetchBranchesByNamespaceAndName, - getBranchNames -} from "../../repos/modules/branches"; import type { PagedCollection, Repository } from "@scm-manager/ui-types"; import ChangesetList from "../components/changesets/ChangesetList"; import DropDown from "../components/DropDown"; import { withRouter } from "react-router-dom"; +import { fetchBranches, getBranchNames } from "../modules/branches"; type Props = { repository: Repository, @@ -53,9 +51,9 @@ class Changesets extends React.PureComponent { } onPageChange = (link: string) => { - const { namespace, name } = this.props.repository; + const { repository } = this.props; const branch = this.props.match.params.branch; - this.props.fetchChangesetsByLink(namespace, name, link, branch); + this.props.fetchChangesetsByLink(repository, link, branch); }; componentDidMount() { @@ -63,24 +61,20 @@ class Changesets extends React.PureComponent { } updateContent() { - const { namespace, name } = this.props.repository; + const { repository } = this.props; + const branchName = this.props.match.params.branch; const { - fetchBranchesByNamespaceAndName, fetchChangesetsByPage, - fetchChangesetsByBranchAndPage + fetchChangesetsByBranchAndPage, + fetchBranches } = this.props; if (branchName) { - fetchChangesetsByBranchAndPage( - namespace, - name, - branchName, - this.props.page - ); + fetchChangesetsByBranchAndPage(repository, branchName, this.props.page); } else { - fetchChangesetsByPage(namespace, name, this.props.page); + fetchChangesetsByPage(repository, this.props.page); } - fetchBranchesByNamespaceAndName(namespace, name); + fetchBranches(repository); } componentDidUpdate(prevProps: Props, prevState: State, snapshot: any) { @@ -149,7 +143,7 @@ class Changesets extends React.PureComponent { ); } - return ; + return ; }; renderPaginator() { @@ -195,13 +189,14 @@ const getPageFromProps = props => { }; const mapStateToProps = (state, ownProps: Props) => { + const { repository } = ownProps; const { namespace, name } = ownProps.repository; const { branch } = ownProps.match.params; const key = createKey(namespace, name, branch); - const loading = isFetchChangesetsPending(state, namespace, name, branch); + const loading = isFetchChangesetsPending(state, repository, branch); const changesets = getChangesetsFromState(state, key); - const branchNames = getBranchNames(namespace, name, state); - const error = getFetchChangesetsFailure(state, namespace, name, branch); + const branchNames = getBranchNames(state, repository); + const error = getFetchChangesetsFailure(state, repository, branch); const list = selectListAsCollection(state, key); const page = getPageFromProps(ownProps); @@ -217,27 +212,28 @@ const mapStateToProps = (state, ownProps: Props) => { const mapDispatchToProps = dispatch => { return { - fetchBranchesByNamespaceAndName: (namespace: string, name: string) => { - dispatch(fetchBranchesByNamespaceAndName(namespace, name)); + fetchBranches: (repository: Repository) => { + dispatch(fetchBranches(repository)); }, - fetchChangesetsByPage: (namespace: string, name: string, page: number) => { - dispatch(fetchChangesetsByPage(namespace, name, page)); + fetchChangesets: (repository: Repository) => { + dispatch(fetchChangesets(repository)); + }, + fetchChangesetsByPage: (repository, page: number) => { + dispatch(fetchChangesetsByPage(repository, page)); }, fetchChangesetsByBranchAndPage: ( - namespace: string, - name: string, + repository, branch: string, page: number ) => { - dispatch(fetchChangesetsByBranchAndPage(namespace, name, branch, page)); + dispatch(fetchChangesetsByBranchAndPage(repository, branch, page)); }, fetchChangesetsByLink: ( - namespace: string, - name: string, + repository: Repository, link: string, branch?: string ) => { - dispatch(fetchChangesetsByLink(namespace, name, link, branch)); + dispatch(fetchChangesetsByLink(repository, link, branch)); } }; }; diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index 1bd8d30df5..9b6a261a85 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -5,62 +5,71 @@ import { SUCCESS_SUFFIX } from "../../modules/types"; import { apiClient } from "@scm-manager/ui-components"; +import type { Repository } from "@scm-manager/ui-types"; 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}`; export const FETCH_BRANCHES_FAILURE = `${FETCH_BRANCHES}_${FAILURE_SUFFIX}`; -const REPO_URL = "repositories"; - // Fetching branches -export function fetchBranchesByNamespaceAndName( - namespace: string, - name: string -) { + +export function fetchBranches(repository: Repository) { return function(dispatch: any) { - dispatch(fetchBranchesPending(namespace, name)); + dispatch(fetchBranchesPending(repository)); return apiClient - .get(REPO_URL + "/" + namespace + "/" + name + "/branches") + .get(repository._links.branches.href) .then(response => response.json()) .then(data => { - dispatch(fetchBranchesSuccess(data, namespace, name)); + dispatch(fetchBranchesSuccess(data, repository)); }) .catch(error => { - dispatch(fetchBranchesFailure(namespace, name, error)); + dispatch(fetchBranchesFailure(repository, error)); }); }; } +// export function fetchBranchesByNamespaceAndName( +// namespace: string, +// name: string +// ) { +// return function(dispatch: any) { +// dispatch(fetchBranchesPending(namespace, name)); +// return apiClient +// .get(REPO_URL + "/" + namespace + "/" + name + "/branches") +// .then(response => response.json()) +// .then(data => { +// dispatch(fetchBranchesSuccess(data, namespace, name)); +// }) +// .catch(error => { +// dispatch(fetchBranchesFailure(namespace, name, error)); +// }); +// }; +// } // Action creators -export function fetchBranchesPending(namespace: string, name: string) { +export function fetchBranchesPending(repository: Repository) { + const { namespace, name } = repository; return { type: FETCH_BRANCHES_PENDING, - payload: { namespace, name }, + payload: { repository }, itemId: namespace + "/" + name }; } -export function fetchBranchesSuccess( - data: string, - namespace: string, - name: string -) { +export function fetchBranchesSuccess(data: string, repository: Repository) { + const { namespace, name } = repository; return { type: FETCH_BRANCHES_SUCCESS, - payload: { data, namespace, name }, + payload: { data, repository }, itemId: namespace + "/" + name }; } -export function fetchBranchesFailure( - namespace: string, - name: string, - error: Error -) { +export function fetchBranchesFailure(repository: Repository, error: Error) { + const { namespace, name } = repository; return { type: FETCH_BRANCHES_FAILURE, - payload: { error, namespace, name }, + payload: { error, repository }, itemId: namespace + "/" + name }; } @@ -73,7 +82,7 @@ export default function reducer( ): Object { switch (action.type) { case FETCH_BRANCHES_SUCCESS: - const key = action.payload.namespace + "/" + action.payload.name; + const key = action.itemId; let oldBranchesByNames = { [key]: {} }; if (state[key] !== undefined) { oldBranchesByNames[key] = state[key]; @@ -119,7 +128,8 @@ export function getBranchesForNamespaceAndNameFromState( return Object.values(state.branches[key].byNames); } -export function getBranchNames(namespace: string, name: string, state: Object) { +export function getBranchNames(state: Object, repository: Repository) { + const { namespace, name } = repository; const key = namespace + "/" + name; if (!state.branches[key] || !state.branches[key].byNames) { return null; diff --git a/scm-ui/src/repos/modules/branches.test.js b/scm-ui/src/repos/modules/branches.test.js index d97d9aa7bf..361032c78a 100644 --- a/scm-ui/src/repos/modules/branches.test.js +++ b/scm-ui/src/repos/modules/branches.test.js @@ -5,148 +5,163 @@ import { FETCH_BRANCHES_FAILURE, FETCH_BRANCHES_PENDING, FETCH_BRANCHES_SUCCESS, - fetchBranchesByNamespaceAndName, getBranchesForNamespaceAndNameFromState, getBranchNames + fetchBranches, + getBranchNames } from "./branches"; import reducer from "./branches"; - const namespace = "foo"; const name = "bar"; const key = namespace + "/" + name; +const repository = { + namespace: "foo", + name: "bar", + _links: { + branches: { + href: "http://scm/api/rest/v2/repositories/foo/bar/branches" + } + } +}; -const branch1 = {name: "branch1", revision: "revision1"}; -const branch2 = {name: "branch2", revision: "revision2"}; -const branch3 = {name: "branch3", revision: "revision3"}; +const branch1 = { name: "branch1", revision: "revision1" }; +const branch2 = { name: "branch2", revision: "revision2" }; +const branch3 = { name: "branch3", revision: "revision3" }; +describe("branches", () => { + describe("fetch branches", () => { + const URL = "http://scm/api/rest/v2/repositories/foo/bar/branches"; + const mockStore = configureMockStore([thunk]); -describe("fetch branches", () => { - const URL = "/api/rest/v2/repositories/foo/bar/branches"; - const mockStore = configureMockStore([thunk]); + afterEach(() => { + fetchMock.reset(); + fetchMock.restore(); + }); + it("should fetch branches", () => { + const collection = {}; - afterEach(() => { - fetchMock.reset(); - fetchMock.restore(); - }); + fetchMock.getOnce(URL, "{}"); + const expectedActions = [ + { + type: FETCH_BRANCHES_PENDING, + payload: { repository }, + itemId: key + }, + { + type: FETCH_BRANCHES_SUCCESS, + payload: { data: collection, repository }, + itemId: key + } + ]; - it("should fetch branches", () => { - const collection = {}; + const store = mockStore({}); + return store.dispatch(fetchBranches(repository)).then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); + }); - fetchMock.getOnce(URL, "{}"); + it("should fail fetching branches on HTTP 500", () => { + const collection = {}; - const expectedActions = [ - { - type: FETCH_BRANCHES_PENDING, payload: {namespace, name}, - itemId: key - }, - { - type: FETCH_BRANCHES_SUCCESS, - payload: {data: collection, namespace, name}, - itemId: key - } - ]; + fetchMock.getOnce(URL, 500); - const store = mockStore({}); - return store.dispatch(fetchBranchesByNamespaceAndName(namespace, name)).then(() => { - expect(store.getActions()).toEqual(expectedActions); + const expectedActions = [ + { + type: FETCH_BRANCHES_PENDING, + payload: { repository }, + itemId: key + }, + { + type: FETCH_BRANCHES_FAILURE, + payload: { error: collection, repository }, + itemId: key + } + ]; + + const store = mockStore({}); + return store.dispatch(fetchBranches(repository)).then(() => { + expect(store.getActions()[0]).toEqual(expectedActions[0]); + expect(store.getActions()[1].type).toEqual(FETCH_BRANCHES_FAILURE); + }); }); }); - it("should fail fetching branches on HTTP 500", () => { - const collection = {}; - - fetchMock.getOnce(URL, 500); - - const expectedActions = [ - { - type: FETCH_BRANCHES_PENDING, payload: {namespace, name}, - itemId: key - }, - { - type: FETCH_BRANCHES_FAILURE, - payload: {error: collection, namespace, name}, - itemId: key + describe("branches reducer", () => { + const branches = { + _embedded: { + branches: [branch1, branch2] } - ]; + }; + const action = { + type: FETCH_BRANCHES_SUCCESS, + payload: { + namespace, + name, + data: branches + } + }; - const store = mockStore({}); - return store.dispatch(fetchBranchesByNamespaceAndName(namespace, name)).then(() => { - expect(store.getActions()[0]).toEqual(expectedActions[0]); - expect(store.getActions()[1].type).toEqual(FETCH_BRANCHES_FAILURE); + it("should update state according to successful fetch", () => { + const newState = reducer({}, action); + expect(newState).toBeDefined(); + expect(newState[key]).toBeDefined(); + expect(newState[key].byNames["branch1"]).toEqual(branch1); + expect(newState[key].byNames["branch2"]).toEqual(branch2); }); - }) -}); -describe("branches reducer", () => { - - const branches = { - _embedded: { - branches: [branch1, branch2] - } - }; - const action = { - type: FETCH_BRANCHES_SUCCESS, - payload: { - namespace, - name, - data: branches - } - }; - - it("should update state according to successful fetch", () => { - const newState = reducer({}, action); - expect(newState).toBeDefined(); - expect(newState[key]).toBeDefined(); - expect(newState[key].byNames["branch1"]).toEqual(branch1); - expect(newState[key].byNames["branch2"]).toEqual(branch2); - }); - - it("should not delete existing branches from state", () => { - const oldState = { - "foo/bar": { byNames: { - "branch3": branch3 - }} - }; - - const newState = reducer(oldState, action); - console.log(newState); - expect(newState[key].byNames["branch1"]).toEqual(branch1); - expect(newState[key].byNames["branch2"]).toEqual(branch2); - expect(newState[key].byNames["branch3"]).toEqual(branch3); - }); -}); - -describe("branch selectors", () => { - it("should get branches for namespace and name", () => { - const state = { - branches: { - [key]: { + it("should not delete existing branches from state", () => { + const oldState = { + "foo/bar": { byNames: { - "branch1": branch1 + branch3: branch3 } } - } - }; - const branches = getBranchesForNamespaceAndNameFromState(namespace, name, state); - expect(branches.length).toEqual(1); - expect(branches[0]).toEqual(branch1); + }; + + const newState = reducer(oldState, action); + console.log(newState); + expect(newState[key].byNames["branch1"]).toEqual(branch1); + expect(newState[key].byNames["branch2"]).toEqual(branch2); + expect(newState[key].byNames["branch3"]).toEqual(branch3); + }); }); - it("should return branches names", () => { - const state = { - branches: { - [key]: { - byNames: { - "branch1": branch1, - "branch2": branch2 + describe("branch selectors", () => { + it("should get branches for namespace and name", () => { + const state = { + branches: { + [key]: { + byNames: { + branch1: branch1 + } } } - } - }; - const names = getBranchNames(namespace, name, state); - expect(names.length).toEqual(2); - expect(names).toContain("branch1"); - expect(names).toContain("branch2"); + }; + const branches = getBranchesForNamespaceAndNameFromState( + namespace, + name, + state + ); + expect(branches.length).toEqual(1); + expect(branches[0]).toEqual(branch1); + }); + + it("should return branches names", () => { + const state = { + branches: { + [key]: { + byNames: { + branch1: branch1, + branch2: branch2 + } + } + } + }; + const names = getBranchNames(state, repository); + expect(names.length).toEqual(2); + expect(names).toContain("branch1"); + expect(names).toContain("branch2"); + }); }); }); diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index bcc323a937..dffc87268d 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -9,7 +9,12 @@ import { apiClient } from "@scm-manager/ui-components"; import { isPending } from "../../modules/pending"; import { getFailure } from "../../modules/failure"; import { combineReducers } from "redux"; -import type { Action, Changeset, PagedCollection } from "@scm-manager/ui-types"; +import type { + Action, + Changeset, + PagedCollection, + Repository +} from "@scm-manager/ui-types"; export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS"; export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; @@ -21,137 +26,122 @@ const REPO_URL = "repositories"; // actions export function fetchChangesetsByLink( - namespace: string, - name: string, + repository: Repository, link: string, branch?: string ) { return function(dispatch: any) { - dispatch(fetchChangesetsPending(namespace, name, branch)); + dispatch(fetchChangesetsPending(repository, branch)); return apiClient .get(link) .then(response => response.json()) .then(data => { - dispatch(fetchChangesetsSuccess(data, namespace, name, branch)); + dispatch(fetchChangesetsSuccess(data, repository, branch)); }) .catch(cause => { - dispatch(fetchChangesetsFailure(namespace, name, cause, branch)); + dispatch(fetchChangesetsFailure(repository, cause, branch)); }); }; } export function fetchChangesetsWithOptions( - namespace: string, - name: string, + repository: Repository, branch?: string, suffix?: string ) { - let link = REPO_URL + `/${namespace}/${name}`; + let link = repository._links.changesets.href; + if (branch && branch !== "") { - link = link + `/branches/${branch}`; + const halBranch = repository._links.branches.find(b => b.name === branch); + link = halBranch._links.history.href; } - link = link + "/changesets"; + if (suffix) { link = link + `${suffix}`; } - return function(dispatch: any) { - dispatch(fetchChangesetsPending(namespace, name, branch)); + dispatch(fetchChangesetsPending(repository, branch)); return apiClient .get(link) .then(response => response.json()) .then(data => { - dispatch(fetchChangesetsSuccess(data, namespace, name, branch)); + dispatch(fetchChangesetsSuccess(data, repository, branch)); }) .catch(cause => { - dispatch(fetchChangesetsFailure(namespace, name, cause, branch)); + dispatch(fetchChangesetsFailure(repository, cause, branch)); }); }; } -export function fetchChangesets(namespace: string, name: string) { - return fetchChangesetsWithOptions(namespace, name); +export function fetchChangesets(repository: Repository) { + return fetchChangesetsWithOptions(repository); } -export function fetchChangesetsByPage( - namespace: string, - name: string, - page: number -) { - return fetchChangesetsWithOptions(namespace, name, "", `?page=${page - 1}`); +export function fetchChangesetsByPage(repository: Repository, page: number) { + return fetchChangesetsWithOptions(repository, "", `?page=${page - 1}`); } +// TODO: Rewrite code to fetch changesets by branches, adjust tests and let BranchChooser fetch branches export function fetchChangesetsByBranchAndPage( - namespace: string, - name: string, + repository: Repository, branch: string, page: number ) { - return fetchChangesetsWithOptions( - namespace, - name, - branch, - `?page=${page - 1}` - ); + return fetchChangesetsWithOptions(repository, branch, `?page=${page - 1}`); } -export function fetchChangesetsByNamespaceNameAndBranch( - namespace: string, - name: string, +export function fetchChangesetsByBranch( + repository: Repository, branch: string ) { - return fetchChangesetsWithOptions(namespace, name, branch); + return fetchChangesetsWithOptions(repository, branch); } export function fetchChangesetsPending( - namespace: string, - name: string, + repository: Repository, branch?: string ): Action { - const itemId = createItemId(namespace, name, branch); + const itemId = createItemId(repository, branch); + if (!branch) { + branch = ""; + } return { type: FETCH_CHANGESETS_PENDING, - payload: itemId, + payload: { repository, branch }, itemId }; } export function fetchChangesetsSuccess( changesets: any, - namespace: string, - name: string, + repository: Repository, branch?: string ): Action { return { type: FETCH_CHANGESETS_SUCCESS, payload: changesets, - itemId: createItemId(namespace, name, branch) + itemId: createItemId(repository, branch) }; } function fetchChangesetsFailure( - namespace: string, - name: string, + repository: Repository, error: Error, branch?: string ): Action { return { type: FETCH_CHANGESETS_FAILURE, payload: { - namespace, - name, + repository, error, branch }, - itemId: createItemId(namespace, name, branch) + itemId: createItemId(repository, branch) }; } -function createItemId( - namespace: string, - name: string, - branch?: string -): string { +function createItemId(repository: Repository, branch?: string): string { + const { namespace, name } = repository; let itemId = namespace + "/" + name; if (branch && branch !== "") { itemId = itemId + "/" + branch; @@ -211,13 +201,8 @@ function extractChangesetsByIds(changesets: any, oldChangesetsByIds: any) { } //selectors -export function getChangesets( - state: Object, - namespace: string, - name: string, - branch?: string -) { - const key = createItemId(namespace, name, branch); +export function getChangesets(state: Object, repository, branch?: string) { + const key = createItemId(repository, branch); if (!state.changesets.byKey[key]) { return null; } @@ -226,28 +211,18 @@ export function getChangesets( export function isFetchChangesetsPending( state: Object, - namespace: string, - name: string, + repository: Repository, branch?: string ) { - return isPending( - state, - FETCH_CHANGESETS, - createItemId(namespace, name, branch) - ); + return isPending(state, FETCH_CHANGESETS, createItemId(repository, branch)); } export function getFetchChangesetsFailure( state: Object, - namespace: string, - name: string, + repository: Repository, branch?: string ) { - return getFailure( - state, - FETCH_CHANGESETS, - createItemId(namespace, name, branch) - ); + return getFailure(state, FETCH_CHANGESETS, createItemId(repository, branch)); } const selectList = (state: Object, key: string) => { diff --git a/scm-ui/src/repos/modules/changesets.test.js b/scm-ui/src/repos/modules/changesets.test.js index 40f18c2423..0b73db5e9d 100644 --- a/scm-ui/src/repos/modules/changesets.test.js +++ b/scm-ui/src/repos/modules/changesets.test.js @@ -10,7 +10,7 @@ import { FETCH_CHANGESETS_SUCCESS, fetchChangesets, fetchChangesetsByBranchAndPage, - fetchChangesetsByNamespaceNameAndBranch, + fetchChangesetsByBranch, fetchChangesetsByPage, fetchChangesetsSuccess, getChangesets, @@ -19,13 +19,38 @@ import { } from "./changesets"; import reducer from "./changesets"; +const repository = { + namespace: "foo", + name: "bar", + _links: { + self: { + href: "http://scm/api/rest/v2/repositories/foo/bar" + }, + changesets: { + href: "http://scm/api/rest/v2/repositories/foo/bar/changesets" + }, + branches: [ + { + name: "specific", + _links: { + history: { + href: + "http://scm/api/rest/v2/repositories/foo/bar/branches/specific/changesets" + } + } + } + ] + } +}; + const changesets = {}; describe("changesets", () => { describe("fetching of changesets", () => { - const DEFAULT_BRANCH_URL = "/api/rest/v2/repositories/foo/bar/changesets"; + const DEFAULT_BRANCH_URL = + "http://scm/api/rest/v2/repositories/foo/bar/changesets"; const SPECIFIC_BRANCH_URL = - "/api/rest/v2/repositories/foo/bar/branches/specific/changesets"; + "http://scm/api/rest/v2/repositories/foo/bar/branches/specific/changesets"; const mockStore = configureMockStore([thunk]); afterEach(() => { @@ -39,7 +64,7 @@ describe("changesets", () => { const expectedActions = [ { type: FETCH_CHANGESETS_PENDING, - payload: "foo/bar", + payload: { repository, branch: "" }, itemId: "foo/bar" }, { @@ -50,7 +75,7 @@ describe("changesets", () => { ]; const store = mockStore({}); - return store.dispatch(fetchChangesets("foo", "bar")).then(() => { + return store.dispatch(fetchChangesets(repository)).then(() => { expect(store.getActions()).toEqual(expectedActions); }); }); @@ -62,7 +87,7 @@ describe("changesets", () => { const expectedActions = [ { type: FETCH_CHANGESETS_PENDING, - payload: itemId, + payload: { repository, branch: "specific" }, itemId }, { @@ -74,9 +99,7 @@ describe("changesets", () => { const store = mockStore({}); return store - .dispatch( - fetchChangesetsByNamespaceNameAndBranch("foo", "bar", "specific") - ) + .dispatch(fetchChangesetsByBranch(repository, "specific")) .then(() => { expect(store.getActions()).toEqual(expectedActions); }); @@ -89,13 +112,13 @@ describe("changesets", () => { const expectedActions = [ { type: FETCH_CHANGESETS_PENDING, - payload: itemId, + payload: { repository, branch: "" }, itemId } ]; const store = mockStore({}); - return store.dispatch(fetchChangesets("foo", "bar")).then(() => { + return store.dispatch(fetchChangesets(repository)).then(() => { expect(store.getActions()[0]).toEqual(expectedActions[0]); expect(store.getActions()[1].type).toEqual(FETCH_CHANGESETS_FAILURE); expect(store.getActions()[1].payload).toBeDefined(); @@ -109,16 +132,14 @@ describe("changesets", () => { const expectedActions = [ { type: FETCH_CHANGESETS_PENDING, - payload: itemId, + payload: { repository, branch: "specific" }, itemId } ]; const store = mockStore({}); return store - .dispatch( - fetchChangesetsByNamespaceNameAndBranch("foo", "bar", "specific") - ) + .dispatch(fetchChangesetsByBranch(repository, "specific")) .then(() => { expect(store.getActions()[0]).toEqual(expectedActions[0]); expect(store.getActions()[1].type).toEqual(FETCH_CHANGESETS_FAILURE); @@ -132,7 +153,7 @@ describe("changesets", () => { const expectedActions = [ { type: FETCH_CHANGESETS_PENDING, - payload: "foo/bar", + payload: { repository, branch: "" }, itemId: "foo/bar" }, { @@ -143,7 +164,7 @@ describe("changesets", () => { ]; const store = mockStore({}); - return store.dispatch(fetchChangesetsByPage("foo", "bar", 5)).then(() => { + return store.dispatch(fetchChangesetsByPage(repository, 5)).then(() => { expect(store.getActions()).toEqual(expectedActions); }); }); @@ -154,7 +175,7 @@ describe("changesets", () => { const expectedActions = [ { type: FETCH_CHANGESETS_PENDING, - payload: "foo/bar/specific", + payload: { repository, branch: "specific" }, itemId: "foo/bar/specific" }, { @@ -166,7 +187,7 @@ describe("changesets", () => { const store = mockStore({}); return store - .dispatch(fetchChangesetsByBranchAndPage("foo", "bar", "specific", 5)) + .dispatch(fetchChangesetsByBranchAndPage(repository, "specific", 5)) .then(() => { expect(store.getActions()).toEqual(expectedActions); }); @@ -195,7 +216,7 @@ describe("changesets", () => { it("should set state to received changesets", () => { const newState = reducer( {}, - fetchChangesetsSuccess(responseBody, "foo", "bar") + fetchChangesetsSuccess(responseBody, repository) ); expect(newState).toBeDefined(); expect(newState.byKey["foo/bar"].byId["changeset1"].author.mail).toEqual( @@ -243,7 +264,7 @@ describe("changesets", () => { } } }, - fetchChangesetsSuccess(responseBody, "foo", "bar") + fetchChangesetsSuccess(responseBody, repository) ); expect(newState.byKey["foo/bar"].byId["changeset2"]).toBeDefined(); expect(newState.byKey["foo/bar"].byId["changeset1"]).toBeDefined(); @@ -253,7 +274,7 @@ describe("changesets", () => { describe("changeset selectors", () => { const error = new Error("Something went wrong"); - it("should get all changesets for a given namespace and name", () => { + it("should get all changesets for a given repository", () => { const state = { changesets: { byKey: { @@ -266,7 +287,7 @@ describe("changesets", () => { } } }; - const result = getChangesets(state, "foo", "bar"); + const result = getChangesets(state, repository); expect(result).toContainEqual({ id: "id1" }); }); @@ -277,11 +298,11 @@ describe("changesets", () => { } }; - expect(isFetchChangesetsPending(state, "foo", "bar")).toBeTruthy(); + expect(isFetchChangesetsPending(state, repository)).toBeTruthy(); }); it("should return false, when fetching changesets is not pending", () => { - expect(isFetchChangesetsPending({}, "foo", "bar")).toEqual(false); + expect(isFetchChangesetsPending({}, repository)).toEqual(false); }); it("should return error if fetching changesets failed", () => { @@ -291,11 +312,11 @@ describe("changesets", () => { } }; - expect(getFetchChangesetsFailure(state, "foo", "bar")).toEqual(error); + expect(getFetchChangesetsFailure(state, repository)).toEqual(error); }); it("should return false if fetching changesets did not fail", () => { - expect(getFetchChangesetsFailure({}, "foo", "bar")).toBeUndefined(); + expect(getFetchChangesetsFailure({}, repository)).toBeUndefined(); }); }); }); From 1f0fea37a920ce4d7b379b84b2196c1589327f77 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Thu, 4 Oct 2018 19:03:03 +0200 Subject: [PATCH 09/52] Fixed unit test --- scm-ui/src/repos/modules/branches.js | 13 +++++------- scm-ui/src/repos/modules/branches.test.js | 26 ++--------------------- 2 files changed, 7 insertions(+), 32 deletions(-) diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index 9b6a261a85..b4cddd63a3 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -1,11 +1,7 @@ // @flow -import { - FAILURE_SUFFIX, - PENDING_SUFFIX, - SUCCESS_SUFFIX -} from "../../modules/types"; -import { apiClient } from "@scm-manager/ui-components"; -import type { Repository } from "@scm-manager/ui-types"; +import {FAILURE_SUFFIX, PENDING_SUFFIX, SUCCESS_SUFFIX} from "../../modules/types"; +import {apiClient} from "@scm-manager/ui-components"; +import type {Repository} from "@scm-manager/ui-types"; export const FETCH_BRANCHES = "scm/repos/FETCH_BRANCHES"; export const FETCH_BRANCHES_PENDING = `${FETCH_BRANCHES}_${PENDING_SUFFIX}`; @@ -82,7 +78,8 @@ export default function reducer( ): Object { switch (action.type) { case FETCH_BRANCHES_SUCCESS: - const key = action.itemId; + const { namespace, name } = action.payload.repository; + const key = `${namespace}/${name}`; let oldBranchesByNames = { [key]: {} }; if (state[key] !== undefined) { oldBranchesByNames[key] = state[key]; diff --git a/scm-ui/src/repos/modules/branches.test.js b/scm-ui/src/repos/modules/branches.test.js index 361032c78a..f82495f736 100644 --- a/scm-ui/src/repos/modules/branches.test.js +++ b/scm-ui/src/repos/modules/branches.test.js @@ -1,14 +1,13 @@ import configureMockStore from "redux-mock-store"; import thunk from "redux-thunk"; import fetchMock from "fetch-mock"; -import { +import reducer, { FETCH_BRANCHES_FAILURE, FETCH_BRANCHES_PENDING, FETCH_BRANCHES_SUCCESS, fetchBranches, getBranchNames } from "./branches"; -import reducer from "./branches"; const namespace = "foo"; const name = "bar"; @@ -96,8 +95,7 @@ describe("branches", () => { const action = { type: FETCH_BRANCHES_SUCCESS, payload: { - namespace, - name, + repository, data: branches } }; @@ -120,7 +118,6 @@ describe("branches", () => { }; const newState = reducer(oldState, action); - console.log(newState); expect(newState[key].byNames["branch1"]).toEqual(branch1); expect(newState[key].byNames["branch2"]).toEqual(branch2); expect(newState[key].byNames["branch3"]).toEqual(branch3); @@ -128,25 +125,6 @@ describe("branches", () => { }); describe("branch selectors", () => { - it("should get branches for namespace and name", () => { - const state = { - branches: { - [key]: { - byNames: { - branch1: branch1 - } - } - } - }; - const branches = getBranchesForNamespaceAndNameFromState( - namespace, - name, - state - ); - expect(branches.length).toEqual(1); - expect(branches[0]).toEqual(branch1); - }); - it("should return branches names", () => { const state = { branches: { From e059762fc4435a1fe3adc6af831a579838c725cb Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Thu, 4 Oct 2018 19:08:11 +0200 Subject: [PATCH 10/52] Fixed flow issues --- scm-ui/src/repos/containers/Changesets.js | 36 ++++++++++++++++------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index 254cfacbe1..d9109bc3b3 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -9,17 +9,21 @@ import { } from "@scm-manager/ui-components"; import { + fetchChangesets, + fetchChangesetsByBranchAndPage, + fetchChangesetsByLink, + fetchChangesetsByPage, + getChangesetsFromState, getFetchChangesetsFailure, isFetchChangesetsPending, - selectListAsCollection, - fetchChangesetsByLink, - getChangesetsFromState, - fetchChangesetsByPage, - fetchChangesetsByBranchAndPage, - fetchChangesets + selectListAsCollection } from "../modules/changesets"; import type { History } from "history"; -import type { PagedCollection, Repository } from "@scm-manager/ui-types"; +import type { + Changeset, + PagedCollection, + Repository +} from "@scm-manager/ui-types"; import ChangesetList from "../components/changesets/ChangesetList"; import DropDown from "../components/DropDown"; import { withRouter } from "react-router-dom"; @@ -28,6 +32,7 @@ import { fetchBranches, getBranchNames } from "../modules/branches"; type Props = { repository: Repository, branchName: string, + branchNames: string[], history: History, fetchChangesetsByNamespaceNameAndBranch: ( namespace: string, @@ -35,19 +40,28 @@ type Props = { branch: string ) => void, list: PagedCollection, - fetchChangesetsByLink: string => void, + fetchChangesetsByLink: (Repository, string, string) => void, + fetchChangesetsByPage: (Repository, number) => void, + fetchChangesetsByBranchAndPage: (Repository, string, number) => void, + fetchBranches: Repository => void, page: number, - t: string => string + t: string => string, + match: any, + changesets: Changeset[], + loading: boolean, + error: boolean }; type State = { branch: string }; -class Changesets extends React.PureComponent { +class Changesets extends React.PureComponent { constructor(props) { super(props); - this.state = {}; + this.state = { + branch: "" + }; } onPageChange = (link: string) => { From eaf89511640955a75f66fa2bf8ca230e9065196d Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Thu, 4 Oct 2018 20:02:18 +0200 Subject: [PATCH 11/52] Boostrapped BranchChooser --- scm-ui/src/repos/components/DropDown.js | 31 ++++++----- scm-ui/src/repos/containers/BranchChooser.js | 54 ++++++++++++++++++++ scm-ui/src/repos/modules/branches.js | 21 ++------ scm-ui/src/repos/modules/branches.test.js | 8 +++ 4 files changed, 84 insertions(+), 30 deletions(-) create mode 100644 scm-ui/src/repos/containers/BranchChooser.js diff --git a/scm-ui/src/repos/components/DropDown.js b/scm-ui/src/repos/components/DropDown.js index d7494d2576..bd44e30f59 100644 --- a/scm-ui/src/repos/components/DropDown.js +++ b/scm-ui/src/repos/components/DropDown.js @@ -6,25 +6,30 @@ type Props = { options: string[], optionSelected: string => void, preselectedOption: string -} +}; class DropDown extends React.Component { render() { - const {options, preselectedOption} = this.props; - return
- -
+ const { options, preselectedOption } = this.props; + return ( +
+ +
+ ); } - change = (event) => { + change = event => { this.props.optionSelected(event.target.value); - } + }; } export default DropDown; diff --git a/scm-ui/src/repos/containers/BranchChooser.js b/scm-ui/src/repos/containers/BranchChooser.js new file mode 100644 index 0000000000..9a802275e2 --- /dev/null +++ b/scm-ui/src/repos/containers/BranchChooser.js @@ -0,0 +1,54 @@ +// @flow + +import React from "react"; +import type {Repository} from "@scm-manager/ui-types"; +import {connect} from "react-redux"; +import {fetchBranches} from "../modules/branches"; +import DropDown from "../components/DropDown"; + +type Props = { + repository: Repository, + fetchBranches: Repository => void, + callback: Branch => void, //TODO use correct branch type + branches: Branch[], //TODO use correct branch type + selectedBranchName: string +}; + +type State = {}; + +class BranchChooser extends React.Component { + componentDidMount() { + const { repository, fetchBranches } = this.props; + fetchBranches(repository); + } + + render() { + const { selectedBranchName, branches } = this.props; + return ( + b.name)} + preselectedOption={selectedBranchName} + optionSelected={branch => this.branchChanged(branch)} + /> + ); + } + + branchChanged = (branchName: string) => {}; +} + +const mapStateToProps = (state: State) => { + return {}; +}; + +const mapDispatchToProps = (dispatch: any) => { + return { + fetchBranches: (repository: Repository) => { + dispatch(fetchBranches(repository)); + } + }; +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(BranchChooser); diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index b4cddd63a3..c121652b68 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -24,23 +24,6 @@ export function fetchBranches(repository: Repository) { }); }; } -// export function fetchBranchesByNamespaceAndName( -// namespace: string, -// name: string -// ) { -// return function(dispatch: any) { -// dispatch(fetchBranchesPending(namespace, name)); -// return apiClient -// .get(REPO_URL + "/" + namespace + "/" + name + "/branches") -// .then(response => response.json()) -// .then(data => { -// dispatch(fetchBranchesSuccess(data, namespace, name)); -// }) -// .catch(error => { -// dispatch(fetchBranchesFailure(namespace, name, error)); -// }); -// }; -// } // Action creators export function fetchBranchesPending(repository: Repository) { @@ -133,3 +116,7 @@ export function getBranchNames(state: Object, repository: Repository) { } return Object.keys(state.branches[key].byNames); } + +export function getBranches(state: Object, repository: Repository) { + return null; +} diff --git a/scm-ui/src/repos/modules/branches.test.js b/scm-ui/src/repos/modules/branches.test.js index f82495f736..98cba858e0 100644 --- a/scm-ui/src/repos/modules/branches.test.js +++ b/scm-ui/src/repos/modules/branches.test.js @@ -141,5 +141,13 @@ describe("branches", () => { expect(names).toContain("branch1"); expect(names).toContain("branch2"); }); + + it("should return branches", () => { + const state = { + branches: { + [key]: {} + } + }; + }); }); }); From 4ce2aae849eb0a1aa3769b7ff60d18dc34702cef Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Fri, 5 Oct 2018 09:55:17 +0200 Subject: [PATCH 12/52] Implemented BranchChooser & selectors --- scm-ui/src/repos/containers/BranchChooser.js | 34 +++++++++---- scm-ui/src/repos/containers/Changesets.js | 9 ++-- scm-ui/src/repos/modules/branches.js | 50 ++++++++++---------- scm-ui/src/repos/modules/branches.test.js | 40 ++++++++++------ scm-ui/src/repos/modules/changesets.js | 6 ++- 5 files changed, 85 insertions(+), 54 deletions(-) diff --git a/scm-ui/src/repos/containers/BranchChooser.js b/scm-ui/src/repos/containers/BranchChooser.js index 9a802275e2..e3b1b0a26a 100644 --- a/scm-ui/src/repos/containers/BranchChooser.js +++ b/scm-ui/src/repos/containers/BranchChooser.js @@ -1,9 +1,9 @@ // @flow import React from "react"; -import type {Repository} from "@scm-manager/ui-types"; -import {connect} from "react-redux"; -import {fetchBranches} from "../modules/branches"; +import type { Repository } from "@scm-manager/ui-types"; +import { connect } from "react-redux"; +import { fetchBranches, getBranch, getBranches } from "../modules/branches"; import DropDown from "../components/DropDown"; type Props = { @@ -14,30 +14,46 @@ type Props = { selectedBranchName: string }; -type State = {}; +type State = { + selectedBranchName: string +}; class BranchChooser extends React.Component { + constructor(props: Props) { + super(props); + this.state = { + selectedBranchName: props.selectedBranchName + }; + } + componentDidMount() { const { repository, fetchBranches } = this.props; fetchBranches(repository); } render() { - const { selectedBranchName, branches } = this.props; + const { branches } = this.props; return ( b.name)} - preselectedOption={selectedBranchName} + preselectedOption={this.state.selectedBranchName} optionSelected={branch => this.branchChanged(branch)} /> ); } - branchChanged = (branchName: string) => {}; + branchChanged = (branchName: string) => { + const { callback } = this.props; + this.setState({ ...this.state, selectedBranchName: branchName }); + const branch = this.props.branches.find(b => b.name === branchName); + callback(branch); + }; } -const mapStateToProps = (state: State) => { - return {}; +const mapStateToProps = (state: State, ownProps: Props) => { + return { + branches: getBranches(state, ownProps.repository) + }; }; const mapDispatchToProps = (dispatch: any) => { diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index d9109bc3b3..58befb8287 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -28,6 +28,7 @@ import ChangesetList from "../components/changesets/ChangesetList"; import DropDown from "../components/DropDown"; import { withRouter } from "react-router-dom"; import { fetchBranches, getBranchNames } from "../modules/branches"; +import BranchChooser from "./BranchChooser"; type Props = { repository: Repository, @@ -148,10 +149,10 @@ class Changesets extends React.PureComponent { - this.branchChanged(branch)} + this.branchChanged(branch.name)} />
diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index c121652b68..5d2a5e4849 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -1,7 +1,11 @@ // @flow -import {FAILURE_SUFFIX, PENDING_SUFFIX, SUCCESS_SUFFIX} from "../../modules/types"; -import {apiClient} from "@scm-manager/ui-components"; -import type {Repository} from "@scm-manager/ui-types"; +import { + FAILURE_SUFFIX, + PENDING_SUFFIX, + SUCCESS_SUFFIX +} from "../../modules/types"; +import { apiClient } from "@scm-manager/ui-components"; +import type { Repository } from "@scm-manager/ui-types"; export const FETCH_BRANCHES = "scm/repos/FETCH_BRANCHES"; export const FETCH_BRANCHES_PENDING = `${FETCH_BRANCHES}_${PENDING_SUFFIX}`; @@ -27,29 +31,26 @@ export function fetchBranches(repository: Repository) { // Action creators export function fetchBranchesPending(repository: Repository) { - const { namespace, name } = repository; return { type: FETCH_BRANCHES_PENDING, payload: { repository }, - itemId: namespace + "/" + name + itemId: createKey(repository) }; } export function fetchBranchesSuccess(data: string, repository: Repository) { - const { namespace, name } = repository; return { type: FETCH_BRANCHES_SUCCESS, payload: { data, repository }, - itemId: namespace + "/" + name + itemId: createKey(repository) }; } export function fetchBranchesFailure(repository: Repository, error: Error) { - const { namespace, name } = repository; return { type: FETCH_BRANCHES_FAILURE, payload: { error, repository }, - itemId: namespace + "/" + name + itemId: createKey(repository) }; } @@ -61,8 +62,7 @@ export default function reducer( ): Object { switch (action.type) { case FETCH_BRANCHES_SUCCESS: - const { namespace, name } = action.payload.repository; - const key = `${namespace}/${name}`; + const key = createKey(action.payload.repository); let oldBranchesByNames = { [key]: {} }; if (state[key] !== undefined) { oldBranchesByNames[key] = state[key]; @@ -96,21 +96,8 @@ function extractBranchesByNames(data: any, oldBranchesByNames: any): Branch[] { // Selectors -export function getBranchesForNamespaceAndNameFromState( - namespace: string, - name: string, - state: Object -) { - const key = namespace + "/" + name; - if (!state.branches[key]) { - return null; - } - return Object.values(state.branches[key].byNames); -} - export function getBranchNames(state: Object, repository: Repository) { - const { namespace, name } = repository; - const key = namespace + "/" + name; + const key = createKey(repository); if (!state.branches[key] || !state.branches[key].byNames) { return null; } @@ -118,5 +105,16 @@ export function getBranchNames(state: Object, repository: Repository) { } export function getBranches(state: Object, repository: Repository) { - return null; + const key = createKey(repository); + return Object.values(state.branches[key].byNames); +} + +export function getBranch(state: Object, repository: Repository, name: string) { + const key = createKey(repository); + return state.branches[key].byNames[name]; +} + +function createKey(repository: Repository) { + const { namespace, name } = repository; + return `${namespace}/${name}`; } diff --git a/scm-ui/src/repos/modules/branches.test.js b/scm-ui/src/repos/modules/branches.test.js index 98cba858e0..8ee96a8603 100644 --- a/scm-ui/src/repos/modules/branches.test.js +++ b/scm-ui/src/repos/modules/branches.test.js @@ -6,6 +6,8 @@ import reducer, { FETCH_BRANCHES_PENDING, FETCH_BRANCHES_SUCCESS, fetchBranches, + getBranch, + getBranches, getBranchNames } from "./branches"; @@ -125,17 +127,18 @@ describe("branches", () => { }); describe("branch selectors", () => { - it("should return branches names", () => { - const state = { - branches: { - [key]: { - byNames: { - branch1: branch1, - branch2: branch2 - } + const state = { + branches: { + [key]: { + byNames: { + branch1, + branch2 } } - }; + } + }; + + it("should return branches names", () => { const names = getBranchNames(state, repository); expect(names.length).toEqual(2); expect(names).toContain("branch1"); @@ -143,11 +146,20 @@ describe("branches", () => { }); it("should return branches", () => { - const state = { - branches: { - [key]: {} - } - }; + const branches = getBranches(state, repository); + expect(branches.length).toEqual(2); + expect(branches).toContain(branch1); + expect(branches).toContain(branch2); + }); + + it("should return single branch by name", () => { + const branch = getBranch(state, repository, "branch1"); + expect(branch).toEqual(branch1); + }); + + it("should return undefined if branch does not exist", () => { + const branch = getBranch(state, repository, "branch42"); + expect(branch).toBeUndefined(); }); }); }); diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index dffc87268d..2283cff171 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -201,7 +201,11 @@ function extractChangesetsByIds(changesets: any, oldChangesetsByIds: any) { } //selectors -export function getChangesets(state: Object, repository, branch?: string) { +export function getChangesets( + state: Object, + repository: Repository, + branch?: string +) { const key = createItemId(repository, branch); if (!state.changesets.byKey[key]) { return null; From 70362918740ef97a7139200300eb8505322534c2 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Fri, 5 Oct 2018 17:13:12 +0200 Subject: [PATCH 13/52] Refactored branches and changesets modules --- scm-ui/src/repos/containers/BranchChooser.js | 18 ++-- scm-ui/src/repos/containers/Changesets.js | 102 +++++++++---------- scm-ui/src/repos/modules/branches.js | 13 ++- scm-ui/src/repos/modules/changesets.js | 24 ++--- scm-ui/src/repos/modules/changesets.test.js | 33 +++--- 5 files changed, 97 insertions(+), 93 deletions(-) diff --git a/scm-ui/src/repos/containers/BranchChooser.js b/scm-ui/src/repos/containers/BranchChooser.js index e3b1b0a26a..5175f37f50 100644 --- a/scm-ui/src/repos/containers/BranchChooser.js +++ b/scm-ui/src/repos/containers/BranchChooser.js @@ -33,13 +33,17 @@ class BranchChooser extends React.Component { render() { const { branches } = this.props; - return ( - b.name)} - preselectedOption={this.state.selectedBranchName} - optionSelected={branch => this.branchChanged(branch)} - /> - ); + if (branches) { + return ( + b.name)} + preselectedOption={this.state.selectedBranchName} + optionSelected={branch => this.branchChanged(branch)} + /> + ); + } + + return null; } branchChanged = (branchName: string) => { diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index 58befb8287..37ac2ba6c5 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -25,9 +25,8 @@ import type { Repository } from "@scm-manager/ui-types"; import ChangesetList from "../components/changesets/ChangesetList"; -import DropDown from "../components/DropDown"; import { withRouter } from "react-router-dom"; -import { fetchBranches, getBranchNames } from "../modules/branches"; +import { fetchBranches, getBranch, getBranchNames } from "../modules/branches"; import BranchChooser from "./BranchChooser"; type Props = { @@ -41,7 +40,7 @@ type Props = { branch: string ) => void, list: PagedCollection, - fetchChangesetsByLink: (Repository, string, string) => void, + fetchChangesetsByLink: (Repository, string, Branch) => void, fetchChangesetsByPage: (Repository, number) => void, fetchChangesetsByBranchAndPage: (Repository, string, number) => void, fetchBranches: Repository => void, @@ -50,7 +49,8 @@ type Props = { match: any, changesets: Changeset[], loading: boolean, - error: boolean + error: boolean, + branch: Branch }; type State = { @@ -58,7 +58,7 @@ type State = { }; class Changesets extends React.PureComponent { - constructor(props) { + constructor(props: Props) { super(props); this.state = { branch: "" @@ -66,8 +66,7 @@ class Changesets extends React.PureComponent { } onPageChange = (link: string) => { - const { repository } = this.props; - const branch = this.props.match.params.branch; + const { repository, branch } = this.props; this.props.fetchChangesetsByLink(repository, link, branch); }; @@ -76,20 +75,18 @@ class Changesets extends React.PureComponent { } updateContent() { - const { repository } = this.props; - - const branchName = this.props.match.params.branch; const { + repository, + branch, + page, fetchChangesetsByPage, - fetchChangesetsByBranchAndPage, - fetchBranches + fetchChangesetsByBranchAndPage } = this.props; - if (branchName) { - fetchChangesetsByBranchAndPage(repository, branchName, this.props.page); + if (branch) { + fetchChangesetsByBranchAndPage(repository, branch, page); } else { - fetchChangesetsByPage(repository, this.props.page); + fetchChangesetsByPage(repository, page); } - fetchBranches(repository); } componentDidUpdate(prevProps: Props, prevState: State, snapshot: any) { @@ -121,7 +118,6 @@ class Changesets extends React.PureComponent { render() { const { changesets, loading, error } = this.props; - if (error) { return ; } @@ -140,27 +136,23 @@ class Changesets extends React.PureComponent { renderList = () => { const branch = this.props.match.params.branch; - const { repository, changesets, branchNames, t } = this.props; + const { repository, changesets, t } = this.props; - if (branchNames && branchNames.length > 0) { - return ( - <> -
- - this.branchChanged(branch.name)} - /> -
- - - ); - } - - return ; + return ( + <> +
+ + this.branchChanged(branch)} + /> +
+ + + ); }; renderPaginator() { @@ -171,11 +163,13 @@ class Changesets extends React.PureComponent { return null; } - branchChanged = (branchName: string): void => { + branchChanged = (branch: Branch): void => { const { history, repository } = this.props; - if (branchName === undefined || branchName === "") { + if (branch === undefined) { history.push(`/repo/${repository.namespace}/${repository.name}/history`); } else { + const branchName = branch.name; + this.setState({ branch: branchName }); history.push( `/repo/${repository.namespace}/${repository.name}/${branchName}/history` ); @@ -183,18 +177,6 @@ class Changesets extends React.PureComponent { }; } -const createKey = ( - namespace: string, - name: string, - branch?: string -): string => { - let key = `${namespace}/${name}`; - if (branch) { - key = key + `/${branch}`; - } - return key; -}; - const getPageFromProps = props => { let page = props.match.params.page; if (page) { @@ -207,9 +189,9 @@ const getPageFromProps = props => { const mapStateToProps = (state, ownProps: Props) => { const { repository } = ownProps; - const { namespace, name } = ownProps.repository; - const { branch } = ownProps.match.params; - const key = createKey(namespace, name, branch); + const branchName = ownProps.match.params.branch; + const branch = getBranch(state, repository, branchName); + const key = createKey(repository, branch); const loading = isFetchChangesetsPending(state, repository, branch); const changesets = getChangesetsFromState(state, key); const branchNames = getBranchNames(state, repository); @@ -223,10 +205,20 @@ const mapStateToProps = (state, ownProps: Props) => { branchNames, error, list, - page + page, + branch }; }; +const createKey = (repository: Repository, branch?: Branch): string => { + const { namespace, name } = repository; + let key = `${namespace}/${name}`; + if (branch && branch.name) { + key = key + `/${branch.name}`; + } + return key; +}; + const mapDispatchToProps = dispatch => { return { fetchBranches: (repository: Repository) => { diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index 5d2a5e4849..2e89aa92f0 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -106,12 +106,21 @@ export function getBranchNames(state: Object, repository: Repository) { export function getBranches(state: Object, repository: Repository) { const key = createKey(repository); - return Object.values(state.branches[key].byNames); + if (state.branches[key]) { + if (state.branches[key].byNames) { + return Object.values(state.branches[key].byNames); + } + } } export function getBranch(state: Object, repository: Repository, name: string) { const key = createKey(repository); - return state.branches[key].byNames[name]; + if (state.branches[key]) { + if (state.branches[key].byNames[name]) { + return state.branches[key].byNames[name]; + } + } + return undefined; } function createKey(repository: Repository) { diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index 2283cff171..e1f56e7a16 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -28,7 +28,7 @@ const REPO_URL = "repositories"; export function fetchChangesetsByLink( repository: Repository, link: string, - branch?: string + branch?: Branch ) { return function(dispatch: any) { dispatch(fetchChangesetsPending(repository, branch)); @@ -46,19 +46,19 @@ export function fetchChangesetsByLink( export function fetchChangesetsWithOptions( repository: Repository, - branch?: string, + branch?: Branch, suffix?: string ) { let link = repository._links.changesets.href; - if (branch && branch !== "") { - const halBranch = repository._links.branches.find(b => b.name === branch); - link = halBranch._links.history.href; + if (branch) { + link = branch._links.history.href; } if (suffix) { link = link + `${suffix}`; } + return function(dispatch: any) { dispatch(fetchChangesetsPending(repository, branch)); return apiClient @@ -84,7 +84,7 @@ export function fetchChangesetsByPage(repository: Repository, page: number) { // TODO: Rewrite code to fetch changesets by branches, adjust tests and let BranchChooser fetch branches export function fetchChangesetsByBranchAndPage( repository: Repository, - branch: string, + branch: Branch, page: number ) { return fetchChangesetsWithOptions(repository, branch, `?page=${page - 1}`); @@ -92,14 +92,14 @@ export function fetchChangesetsByBranchAndPage( export function fetchChangesetsByBranch( repository: Repository, - branch: string + branch: Branch ) { return fetchChangesetsWithOptions(repository, branch); } export function fetchChangesetsPending( repository: Repository, - branch?: string + branch?: Branch ): Action { const itemId = createItemId(repository, branch); if (!branch) { @@ -115,7 +115,7 @@ export function fetchChangesetsPending( export function fetchChangesetsSuccess( changesets: any, repository: Repository, - branch?: string + branch?: Branch ): Action { return { type: FETCH_CHANGESETS_SUCCESS, @@ -127,7 +127,7 @@ export function fetchChangesetsSuccess( function fetchChangesetsFailure( repository: Repository, error: Error, - branch?: string + branch?: Branch ): Action { return { type: FETCH_CHANGESETS_FAILURE, @@ -140,11 +140,11 @@ function fetchChangesetsFailure( }; } -function createItemId(repository: Repository, branch?: string): string { +function createItemId(repository: Repository, branch?: Branch): string { const { namespace, name } = repository; let itemId = namespace + "/" + name; if (branch && branch !== "") { - itemId = itemId + "/" + branch; + itemId = itemId + "/" + branch.name; } return itemId; } diff --git a/scm-ui/src/repos/modules/changesets.test.js b/scm-ui/src/repos/modules/changesets.test.js index 0b73db5e9d..3512921db0 100644 --- a/scm-ui/src/repos/modules/changesets.test.js +++ b/scm-ui/src/repos/modules/changesets.test.js @@ -18,6 +18,15 @@ import { isFetchChangesetsPending } from "./changesets"; import reducer from "./changesets"; +const branch = { + name: "specific", + _links: { + history: { + href: + "http://scm/api/rest/v2/repositories/foo/bar/branches/specific/changesets" + } + } +}; const repository = { namespace: "foo", @@ -29,17 +38,7 @@ const repository = { changesets: { href: "http://scm/api/rest/v2/repositories/foo/bar/changesets" }, - branches: [ - { - name: "specific", - _links: { - history: { - href: - "http://scm/api/rest/v2/repositories/foo/bar/branches/specific/changesets" - } - } - } - ] + branches: [branch] } }; @@ -87,7 +86,7 @@ describe("changesets", () => { const expectedActions = [ { type: FETCH_CHANGESETS_PENDING, - payload: { repository, branch: "specific" }, + payload: { repository, branch }, itemId }, { @@ -99,7 +98,7 @@ describe("changesets", () => { const store = mockStore({}); return store - .dispatch(fetchChangesetsByBranch(repository, "specific")) + .dispatch(fetchChangesetsByBranch(repository, branch)) .then(() => { expect(store.getActions()).toEqual(expectedActions); }); @@ -132,14 +131,14 @@ describe("changesets", () => { const expectedActions = [ { type: FETCH_CHANGESETS_PENDING, - payload: { repository, branch: "specific" }, + payload: { repository, branch }, itemId } ]; const store = mockStore({}); return store - .dispatch(fetchChangesetsByBranch(repository, "specific")) + .dispatch(fetchChangesetsByBranch(repository, branch)) .then(() => { expect(store.getActions()[0]).toEqual(expectedActions[0]); expect(store.getActions()[1].type).toEqual(FETCH_CHANGESETS_FAILURE); @@ -175,7 +174,7 @@ describe("changesets", () => { const expectedActions = [ { type: FETCH_CHANGESETS_PENDING, - payload: { repository, branch: "specific" }, + payload: { repository, branch }, itemId: "foo/bar/specific" }, { @@ -187,7 +186,7 @@ describe("changesets", () => { const store = mockStore({}); return store - .dispatch(fetchChangesetsByBranchAndPage(repository, "specific", 5)) + .dispatch(fetchChangesetsByBranchAndPage(repository, branch, 5)) .then(() => { expect(store.getActions()).toEqual(expectedActions); }); From e58269444b0838fbc558abf87f8e491539ebda9a Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Mon, 8 Oct 2018 12:54:11 +0200 Subject: [PATCH 14/52] Fixed issues preventing paged changeset list to be accessed via URL --- scm-ui/src/repos/containers/BranchChooser.js | 21 +++++--- scm-ui/src/repos/containers/Changesets.js | 56 +++++++++----------- scm-ui/src/repos/modules/branches.js | 5 ++ scm-ui/src/repos/modules/branches.test.js | 14 ++++- scm-ui/src/repos/modules/changesets.js | 22 +++----- 5 files changed, 64 insertions(+), 54 deletions(-) diff --git a/scm-ui/src/repos/containers/BranchChooser.js b/scm-ui/src/repos/containers/BranchChooser.js index 5175f37f50..d8d8aae61e 100644 --- a/scm-ui/src/repos/containers/BranchChooser.js +++ b/scm-ui/src/repos/containers/BranchChooser.js @@ -1,9 +1,11 @@ // @flow import React from "react"; -import type { Repository } from "@scm-manager/ui-types"; -import { connect } from "react-redux"; -import { fetchBranches, getBranch, getBranches } from "../modules/branches"; +import type {Repository} from "@scm-manager/ui-types"; +import {connect} from "react-redux"; +import {fetchBranches, getBranches, isFetchBranchesPending} from "../modules/branches"; + +import {Loading} from "@scm-manager/ui-components"; import DropDown from "../components/DropDown"; type Props = { @@ -11,7 +13,8 @@ type Props = { fetchBranches: Repository => void, callback: Branch => void, //TODO use correct branch type branches: Branch[], //TODO use correct branch type - selectedBranchName: string + selectedBranchName: string, + loading: boolean }; type State = { @@ -32,8 +35,11 @@ class BranchChooser extends React.Component { } render() { - const { branches } = this.props; - if (branches) { + const { branches, loading } = this.props; + if (loading) { + return ; + } + if (branches && branches.length > 0) { return ( b.name)} @@ -56,7 +62,8 @@ class BranchChooser extends React.Component { const mapStateToProps = (state: State, ownProps: Props) => { return { - branches: getBranches(state, ownProps.repository) + branches: getBranches(state, ownProps.repository), + loading: isFetchBranchesPending(state, ownProps.repository) }; }; diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index 37ac2ba6c5..0dc85320b5 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -1,12 +1,8 @@ // @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 {connect} from "react-redux"; +import {translate} from "react-i18next"; +import {ErrorNotification, Loading, Paginator} from "@scm-manager/ui-components"; import { fetchChangesets, @@ -18,15 +14,11 @@ import { isFetchChangesetsPending, selectListAsCollection } from "../modules/changesets"; -import type { History } from "history"; -import type { - Changeset, - PagedCollection, - Repository -} from "@scm-manager/ui-types"; +import type {History} from "history"; +import type {Changeset, PagedCollection, Repository} from "@scm-manager/ui-types"; import ChangesetList from "../components/changesets/ChangesetList"; -import { withRouter } from "react-router-dom"; -import { fetchBranches, getBranch, getBranchNames } from "../modules/branches"; +import {withRouter} from "react-router-dom"; +import {fetchBranches, getBranch, getBranchNames} from "../modules/branches"; import BranchChooser from "./BranchChooser"; type Props = { @@ -94,23 +86,25 @@ class Changesets extends React.PureComponent { const { namespace, name } = repository; const branch = match.params.branch; - if (branch !== prevState.branch) { - this.updateContent(); - this.setState({ branch }); - } + if (!this.props.loading) { + if (prevProps.branch !== this.props.branch) { + this.updateContent(); + this.setState({ branch }); + } - if (list && (list.page || list.page === 0)) { - // backend starts paging at 0 - const statePage: number = list.page + 1; - if (page !== statePage) { - if (branch) { - this.props.history.push( - `/repo/${namespace}/${name}/${branch}/history/${statePage}` - ); - } else { - this.props.history.push( - `/repo/${namespace}/${name}/history/${statePage}` - ); + if (list && (list.page || list.page === 0)) { + // backend starts paging at 0 + const statePage: number = list.page + 1; + if (page !== statePage) { + if (branch) { + this.props.history.push( + `/repo/${namespace}/${name}/${branch}/history/${statePage}` + ); + } else { + this.props.history.push( + `/repo/${namespace}/${name}/history/${statePage}` + ); + } } } } diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index 2e89aa92f0..5f079ff8fd 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -6,6 +6,7 @@ import { } from "../../modules/types"; import { apiClient } from "@scm-manager/ui-components"; import type { Repository } from "@scm-manager/ui-types"; +import { isPending } from "../../modules/pending"; export const FETCH_BRANCHES = "scm/repos/FETCH_BRANCHES"; export const FETCH_BRANCHES_PENDING = `${FETCH_BRANCHES}_${PENDING_SUFFIX}`; @@ -123,6 +124,10 @@ export function getBranch(state: Object, repository: Repository, name: string) { return undefined; } +export function isFetchBranchesPending(state: Object, repository: Repository) { + return isPending(state, FETCH_BRANCHES, createKey(repository)); +} + function createKey(repository: Repository) { const { namespace, name } = repository; return `${namespace}/${name}`; diff --git a/scm-ui/src/repos/modules/branches.test.js b/scm-ui/src/repos/modules/branches.test.js index 8ee96a8603..cfae7da564 100644 --- a/scm-ui/src/repos/modules/branches.test.js +++ b/scm-ui/src/repos/modules/branches.test.js @@ -2,13 +2,15 @@ import configureMockStore from "redux-mock-store"; import thunk from "redux-thunk"; import fetchMock from "fetch-mock"; import reducer, { + FETCH_BRANCHES, FETCH_BRANCHES_FAILURE, FETCH_BRANCHES_PENDING, FETCH_BRANCHES_SUCCESS, fetchBranches, getBranch, getBranches, - getBranchNames + getBranchNames, + isFetchBranchesPending } from "./branches"; const namespace = "foo"; @@ -138,6 +140,16 @@ describe("branches", () => { } }; + it("should return true, when fetching branches is pending", () => { + const state = { + pending: { + [FETCH_BRANCHES + "/foo/bar"]: true + } + }; + + expect(isFetchBranchesPending(state, repository)).toBeTruthy(); + }); + it("should return branches names", () => { const names = getBranchNames(state, repository); expect(names.length).toEqual(2); diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index e1f56e7a16..4cb71af9cd 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -1,20 +1,11 @@ // @flow -import { - FAILURE_SUFFIX, - PENDING_SUFFIX, - SUCCESS_SUFFIX -} from "../../modules/types"; -import { apiClient } from "@scm-manager/ui-components"; -import { isPending } from "../../modules/pending"; -import { getFailure } from "../../modules/failure"; -import { combineReducers } from "redux"; -import type { - Action, - Changeset, - PagedCollection, - Repository -} from "@scm-manager/ui-types"; +import {FAILURE_SUFFIX, PENDING_SUFFIX, SUCCESS_SUFFIX} from "../../modules/types"; +import {apiClient} from "@scm-manager/ui-components"; +import {isPending} from "../../modules/pending"; +import {getFailure} from "../../modules/failure"; +import {combineReducers} from "redux"; +import type {Action, Changeset, PagedCollection, Repository} from "@scm-manager/ui-types"; export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS"; export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; @@ -165,6 +156,7 @@ function byKeyReducer( } const byIds = extractChangesetsByIds(changesets, oldChangesets[key].byId); return { + ...state, [key]: { byId: { ...byIds }, list: { From dbe9ee59a8fc6a4d5e985867215ca412751f075b Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Mon, 8 Oct 2018 17:34:11 +0200 Subject: [PATCH 15/52] Fixed issues noticed in review --- .../ui-types/src/{Branch.js => Branches.js} | 0 .../packages/ui-types/src/Changesets.js | 10 +-- .../packages/ui-types/src/index.js | 4 +- scm-ui/public/locales/en/repos.json | 2 +- .../components/changesets/ChangesetAuthor.js | 26 ++++--- .../components/changesets/ChangesetAvatar.js | 3 +- .../components/changesets/ChangesetList.js | 10 ++- .../components/changesets/ChangesetRow.js | 7 +- scm-ui/src/repos/containers/BranchChooser.js | 16 +++-- scm-ui/src/repos/containers/Changesets.js | 70 ++++++++++--------- scm-ui/src/repos/containers/RepositoryRoot.js | 10 +-- scm-ui/src/repos/modules/branches.js | 35 ++++++++-- scm-ui/src/repos/modules/changesets.js | 63 ++++++++++------- scm-ui/src/repos/modules/changesets.test.js | 8 ++- 14 files changed, 160 insertions(+), 104 deletions(-) rename scm-ui-components/packages/ui-types/src/{Branch.js => Branches.js} (100%) diff --git a/scm-ui-components/packages/ui-types/src/Branch.js b/scm-ui-components/packages/ui-types/src/Branches.js similarity index 100% rename from scm-ui-components/packages/ui-types/src/Branch.js rename to scm-ui-components/packages/ui-types/src/Branches.js diff --git a/scm-ui-components/packages/ui-types/src/Changesets.js b/scm-ui-components/packages/ui-types/src/Changesets.js index 12b87173fe..cab4233a7f 100644 --- a/scm-ui-components/packages/ui-types/src/Changesets.js +++ b/scm-ui-components/packages/ui-types/src/Changesets.js @@ -1,21 +1,21 @@ //@flow import type {Links} from "./hal"; import type {Tag} from "./Tags"; -import type {Branch} from "./Branch"; +import type {Branch} from "./Branches"; export type Changeset = { id: string, date: Date, author: { name: string, - mail: string + mail?: string }, description: string, _links: Links, _embedded: { - tags: Tag[], - branches: Branch[], - parents: ParentChangeset[] + tags?: Tag[], + branches?: Branch[], + parents?: ParentChangeset[] }; } diff --git a/scm-ui-components/packages/ui-types/src/index.js b/scm-ui-components/packages/ui-types/src/index.js index 5e9c14e758..7aea444abb 100644 --- a/scm-ui-components/packages/ui-types/src/index.js +++ b/scm-ui-components/packages/ui-types/src/index.js @@ -9,8 +9,10 @@ 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 { Changeset } from "./Changesets"; -export type { Tag } from "./Tags" +export type { Tag } from "./Tags"; export type { Config } from "./Config"; diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 67732372a3..b55428e14d 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -23,7 +23,7 @@ "back-label": "Back", "navigation-label": "Navigation", "information": "Information", - "history": "History" + "history": "Commits" }, "create": { "title": "Create Repository", diff --git a/scm-ui/src/repos/components/changesets/ChangesetAuthor.js b/scm-ui/src/repos/components/changesets/ChangesetAuthor.js index d2993daed4..0007b24f31 100644 --- a/scm-ui/src/repos/components/changesets/ChangesetAuthor.js +++ b/scm-ui/src/repos/components/changesets/ChangesetAuthor.js @@ -9,19 +9,25 @@ type Props = { export default class ChangesetAuthor extends React.Component { render() { - const { changeset } = this.props; + const { name } = this.props.changeset.author; + return ( <> - {changeset.author.name}{" "} - - < - {changeset.author.mail} - > - + {name} {this.renderMail()} ); } + + renderMail() { + const { mail } = this.props.changeset.author; + if (mail) { + return ( + + < + {mail} + > + + ); + } + } } diff --git a/scm-ui/src/repos/components/changesets/ChangesetAvatar.js b/scm-ui/src/repos/components/changesets/ChangesetAvatar.js index 0a5afc1778..90f116daed 100644 --- a/scm-ui/src/repos/components/changesets/ChangesetAvatar.js +++ b/scm-ui/src/repos/components/changesets/ChangesetAvatar.js @@ -1,8 +1,7 @@ //@flow import React from "react"; import { ExtensionPoint } from "@scm-manager/ui-extensions"; -import type { Changeset } from "../../../../../scm-ui-components/packages/ui-types/src/index"; -import { Image } from "../../../../../scm-ui-components/packages/ui-components/src/index"; +import type { Changeset } from "@scm-manager/ui-types"; type Props = { changeset: Changeset diff --git a/scm-ui/src/repos/components/changesets/ChangesetList.js b/scm-ui/src/repos/components/changesets/ChangesetList.js index 4dd1762e1c..1eaae67ff0 100644 --- a/scm-ui/src/repos/components/changesets/ChangesetList.js +++ b/scm-ui/src/repos/components/changesets/ChangesetList.js @@ -1,7 +1,7 @@ // @flow import ChangesetRow from "./ChangesetRow"; import React from "react"; -import type { Changeset, Repository } from "../../../../../scm-ui-components/packages/ui-types/src/index"; +import type { Changeset, Repository } from "@scm-manager/ui-types"; import classNames from "classnames"; type Props = { @@ -13,7 +13,13 @@ class ChangesetList extends React.Component { render() { const { repository, changesets } = this.props; const content = changesets.map((changeset, index) => { - return ; + return ( + + ); }); return
{content}
; } diff --git a/scm-ui/src/repos/components/changesets/ChangesetRow.js b/scm-ui/src/repos/components/changesets/ChangesetRow.js index 7ad8c92650..675bbe7cda 100644 --- a/scm-ui/src/repos/components/changesets/ChangesetRow.js +++ b/scm-ui/src/repos/components/changesets/ChangesetRow.js @@ -1,15 +1,12 @@ //@flow import React from "react"; -import type { - Changeset, - Repository -} from "../../../../../scm-ui-components/packages/ui-types/src/index"; +import type { Changeset, Repository } from "@scm-manager/ui-types"; import classNames from "classnames"; import { translate, Interpolate } from "react-i18next"; import ChangesetAvatar from "./ChangesetAvatar"; import ChangesetId from "./ChangesetId"; import injectSheet from "react-jss"; -import { DateFromNow } from "../../../../../scm-ui-components/packages/ui-components/src/index"; +import { DateFromNow } from "@scm-manager/ui-components"; import ChangesetAuthor from "./ChangesetAuthor"; const styles = { diff --git a/scm-ui/src/repos/containers/BranchChooser.js b/scm-ui/src/repos/containers/BranchChooser.js index d8d8aae61e..607d96882c 100644 --- a/scm-ui/src/repos/containers/BranchChooser.js +++ b/scm-ui/src/repos/containers/BranchChooser.js @@ -1,18 +1,22 @@ // @flow import React from "react"; -import type {Repository} from "@scm-manager/ui-types"; -import {connect} from "react-redux"; -import {fetchBranches, getBranches, isFetchBranchesPending} from "../modules/branches"; +import type { Repository, Branch } from "@scm-manager/ui-types"; +import { connect } from "react-redux"; +import { + fetchBranches, + getBranches, + isFetchBranchesPending +} from "../modules/branches"; -import {Loading} from "@scm-manager/ui-components"; +import { Loading } from "@scm-manager/ui-components"; import DropDown from "../components/DropDown"; type Props = { repository: Repository, fetchBranches: Repository => void, - callback: Branch => void, //TODO use correct branch type - branches: Branch[], //TODO use correct branch type + callback: (?Branch) => void, + branches: Branch[], selectedBranchName: string, loading: boolean }; diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index 0dc85320b5..7e7dcc33f3 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -1,8 +1,12 @@ // @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 { connect } from "react-redux"; +import { translate } from "react-i18next"; +import { + ErrorNotification, + Loading, + Paginator +} from "@scm-manager/ui-components"; import { fetchChangesets, @@ -14,11 +18,16 @@ import { isFetchChangesetsPending, selectListAsCollection } from "../modules/changesets"; -import type {History} from "history"; -import type {Changeset, PagedCollection, Repository} from "@scm-manager/ui-types"; +import type { History } from "history"; +import type { + Changeset, + PagedCollection, + Repository, + Branch +} from "@scm-manager/ui-types"; import ChangesetList from "../components/changesets/ChangesetList"; -import {withRouter} from "react-router-dom"; -import {fetchBranches, getBranch, getBranchNames} from "../modules/branches"; +import { withRouter } from "react-router-dom"; +import { fetchBranches, getBranch, getBranchNames } from "../modules/branches"; import BranchChooser from "./BranchChooser"; type Props = { @@ -34,7 +43,7 @@ type Props = { list: PagedCollection, fetchChangesetsByLink: (Repository, string, Branch) => void, fetchChangesetsByPage: (Repository, number) => void, - fetchChangesetsByBranchAndPage: (Repository, string, number) => void, + fetchChangesetsByBranchAndPage: (Repository, Branch, number) => void, fetchBranches: Repository => void, page: number, t: string => string, @@ -63,7 +72,9 @@ class Changesets extends React.PureComponent { }; componentDidMount() { - this.updateContent(); + if (!this.props.loading) { + this.updateContent(); + } } updateContent() { @@ -81,15 +92,14 @@ class Changesets extends React.PureComponent { } } - componentDidUpdate(prevProps: Props, prevState: State, snapshot: any) { + componentDidUpdate(prevProps: Props) { const { page, list, repository, match } = this.props; const { namespace, name } = repository; - const branch = match.params.branch; - + const branch = decodeURIComponent(match.params.branch); if (!this.props.loading) { if (prevProps.branch !== this.props.branch) { - this.updateContent(); this.setState({ branch }); + this.updateContent(); } if (list && (list.page || list.page === 0)) { @@ -98,11 +108,11 @@ class Changesets extends React.PureComponent { if (page !== statePage) { if (branch) { this.props.history.push( - `/repo/${namespace}/${name}/${branch}/history/${statePage}` + `/repo/${namespace}/${name}/${branch}/changesets/${statePage}` ); } else { this.props.history.push( - `/repo/${namespace}/${name}/history/${statePage}` + `/repo/${namespace}/${name}/changesets/${statePage}` ); } } @@ -129,7 +139,7 @@ class Changesets extends React.PureComponent { } renderList = () => { - const branch = this.props.match.params.branch; + const branch = decodeURIComponent(this.props.match.params.branch); const { repository, changesets, t } = this.props; return ( @@ -160,12 +170,16 @@ class Changesets extends React.PureComponent { branchChanged = (branch: Branch): void => { const { history, repository } = this.props; if (branch === undefined) { - history.push(`/repo/${repository.namespace}/${repository.name}/history`); + history.push( + `/repo/${repository.namespace}/${repository.name}/changesets` + ); } else { - const branchName = branch.name; + const branchName = encodeURIComponent(branch.name); this.setState({ branch: branchName }); history.push( - `/repo/${repository.namespace}/${repository.name}/${branchName}/history` + `/repo/${repository.namespace}/${ + repository.name + }/${branchName}/changesets` ); } }; @@ -185,12 +199,11 @@ const mapStateToProps = (state, ownProps: Props) => { const { repository } = ownProps; const branchName = ownProps.match.params.branch; const branch = getBranch(state, repository, branchName); - const key = createKey(repository, branch); const loading = isFetchChangesetsPending(state, repository, branch); - const changesets = getChangesetsFromState(state, key); + const changesets = getChangesetsFromState(state, repository); const branchNames = getBranchNames(state, repository); const error = getFetchChangesetsFailure(state, repository, branch); - const list = selectListAsCollection(state, key); + const list = selectListAsCollection(state, repository); const page = getPageFromProps(ownProps); return { @@ -204,15 +217,6 @@ const mapStateToProps = (state, ownProps: Props) => { }; }; -const createKey = (repository: Repository, branch?: Branch): string => { - const { namespace, name } = repository; - let key = `${namespace}/${name}`; - if (branch && branch.name) { - key = key + `/${branch.name}`; - } - return key; -}; - const mapDispatchToProps = dispatch => { return { fetchBranches: (repository: Repository) => { @@ -226,7 +230,7 @@ const mapDispatchToProps = dispatch => { }, fetchChangesetsByBranchAndPage: ( repository, - branch: string, + branch: Branch, page: number ) => { dispatch(fetchChangesetsByBranchAndPage(repository, branch, page)); @@ -234,7 +238,7 @@ const mapDispatchToProps = dispatch => { fetchChangesetsByLink: ( repository: Repository, link: string, - branch?: string + branch?: Branch ) => { dispatch(fetchChangesetsByLink(repository, link, branch)); } diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index b274540370..ec780a5bda 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -104,22 +104,22 @@ class RepositoryRoot extends React.Component { /> } /> } /> } /> } />
@@ -129,7 +129,7 @@ class RepositoryRoot extends React.Component { diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index 5f079ff8fd..6d70ae0925 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -5,7 +5,7 @@ import { SUCCESS_SUFFIX } from "../../modules/types"; import { apiClient } from "@scm-manager/ui-components"; -import type { Repository } from "@scm-manager/ui-types"; +import type { Repository, Action, Branch } from "@scm-manager/ui-types"; import { isPending } from "../../modules/pending"; export const FETCH_BRANCHES = "scm/repos/FETCH_BRANCHES"; @@ -16,6 +16,14 @@ export const FETCH_BRANCHES_FAILURE = `${FETCH_BRANCHES}_${FAILURE_SUFFIX}`; // Fetching branches export function fetchBranches(repository: Repository) { + if (!repository._links.branches) { + return { + type: FETCH_BRANCHES_SUCCESS, + payload: { repository, data: {} }, + itemId: createKey(repository) + }; + } + return function(dispatch: any) { dispatch(fetchBranchesPending(repository)); return apiClient @@ -81,7 +89,10 @@ export default function reducer( } } -function extractBranchesByNames(data: any, oldBranchesByNames: any): Branch[] { +function extractBranchesByNames(data: any, oldBranchesByNames: any): ?Object { + if (!data._embedded || !data._embedded.branches) { + return {}; + } const branches = data._embedded.branches; const branchesByNames = {}; @@ -97,10 +108,13 @@ function extractBranchesByNames(data: any, oldBranchesByNames: any): Branch[] { // Selectors -export function getBranchNames(state: Object, repository: Repository) { +export function getBranchNames( + state: Object, + repository: Repository +): ?Array { const key = createKey(repository); if (!state.branches[key] || !state.branches[key].byNames) { - return null; + return []; } return Object.keys(state.branches[key].byNames); } @@ -114,7 +128,11 @@ export function getBranches(state: Object, repository: Repository) { } } -export function getBranch(state: Object, repository: Repository, name: string) { +export function getBranch( + state: Object, + repository: Repository, + name: string +): ?Branch { const key = createKey(repository); if (state.branches[key]) { if (state.branches[key].byNames[name]) { @@ -124,11 +142,14 @@ export function getBranch(state: Object, repository: Repository, name: string) { return undefined; } -export function isFetchBranchesPending(state: Object, repository: Repository) { +export function isFetchBranchesPending( + state: Object, + repository: Repository +): boolean { return isPending(state, FETCH_BRANCHES, createKey(repository)); } -function createKey(repository: Repository) { +function createKey(repository: Repository): string { const { namespace, name } = repository; return `${namespace}/${name}`; } diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index 4cb71af9cd..e7e7601196 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -1,11 +1,21 @@ // @flow -import {FAILURE_SUFFIX, PENDING_SUFFIX, SUCCESS_SUFFIX} from "../../modules/types"; -import {apiClient} from "@scm-manager/ui-components"; -import {isPending} from "../../modules/pending"; -import {getFailure} from "../../modules/failure"; -import {combineReducers} from "redux"; -import type {Action, Changeset, PagedCollection, Repository} from "@scm-manager/ui-types"; +import { + FAILURE_SUFFIX, + PENDING_SUFFIX, + SUCCESS_SUFFIX +} from "../../modules/types"; +import { apiClient } from "@scm-manager/ui-components"; +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_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; @@ -69,7 +79,7 @@ export function fetchChangesets(repository: Repository) { } export function fetchChangesetsByPage(repository: Repository, page: number) { - return fetchChangesetsWithOptions(repository, "", `?page=${page - 1}`); + return fetchChangesetsWithOptions(repository, undefined, `?page=${page - 1}`); } // TODO: Rewrite code to fetch changesets by branches, adjust tests and let BranchChooser fetch branches @@ -93,9 +103,7 @@ export function fetchChangesetsPending( branch?: Branch ): Action { const itemId = createItemId(repository, branch); - if (!branch) { - branch = ""; - } + return { type: FETCH_CHANGESETS_PENDING, payload: { repository, branch }, @@ -150,8 +158,13 @@ function byKeyReducer( const changesets = action.payload._embedded.changesets; const changesetIds = changesets.map(c => c.id); const key = action.itemId; + + if (!key) { + return state; + } + let oldChangesets = { [key]: {} }; - if (state[key] !== undefined) { + if (state[key]) { oldChangesets[key] = state[key]; } const byIds = extractChangesetsByIds(changesets, oldChangesets[key].byId); @@ -196,7 +209,7 @@ function extractChangesetsByIds(changesets: any, oldChangesetsByIds: any) { export function getChangesets( state: Object, repository: Repository, - branch?: string + branch?: Branch ) { const key = createItemId(repository, branch); if (!state.changesets.byKey[key]) { @@ -208,7 +221,7 @@ export function getChangesets( export function isFetchChangesetsPending( state: Object, repository: Repository, - branch?: string + branch?: Branch ) { return isPending(state, FETCH_CHANGESETS, createItemId(repository, branch)); } @@ -216,20 +229,21 @@ export function isFetchChangesetsPending( export function getFetchChangesetsFailure( state: Object, repository: Repository, - branch?: string + branch?: Branch ) { return getFailure(state, FETCH_CHANGESETS, createItemId(repository, branch)); } -const selectList = (state: Object, key: string) => { - if (state.changesets.byKey[key] && state.changesets.byKey[key].list) { - return state.changesets.byKey[key].list; +const selectList = (state: Object, repository: Repository) => { + const itemId = createItemId(repository); + if (state.changesets.byKey[itemId] && state.changesets.byKey[itemId].list) { + return state.changesets.byKey[itemId].list; } return {}; }; -const selectListEntry = (state: Object, key: string): Object => { - const list = selectList(state, key); +const selectListEntry = (state: Object, repository: Repository): Object => { + const list = selectList(state, repository); if (list.entry) { return list.entry; } @@ -238,20 +252,21 @@ const selectListEntry = (state: Object, key: string): Object => { export const selectListAsCollection = ( state: Object, - key: string + repository: Repository ): PagedCollection => { - return selectListEntry(state, key); + return selectListEntry(state, repository); }; -export function getChangesetsFromState(state: Object, key: string) { - const changesetIds = selectList(state, key).entries; +export function getChangesetsFromState(state: Object, repository: Repository) { + const itemId = createItemId(repository); + const changesetIds = selectList(state, repository).entries; if (!changesetIds) { return null; } const changesetEntries: Changeset[] = []; for (let id of changesetIds) { - changesetEntries.push(state.changesets.byKey[key].byId[id]); + changesetEntries.push(state.changesets.byKey[itemId].byId[id]); } return changesetEntries; diff --git a/scm-ui/src/repos/modules/changesets.test.js b/scm-ui/src/repos/modules/changesets.test.js index 3512921db0..6a557cf1ac 100644 --- a/scm-ui/src/repos/modules/changesets.test.js +++ b/scm-ui/src/repos/modules/changesets.test.js @@ -20,6 +20,7 @@ import { import reducer from "./changesets"; const branch = { name: "specific", + revision: "123", _links: { history: { href: @@ -31,6 +32,7 @@ const branch = { const repository = { namespace: "foo", name: "bar", + type: "GIT", _links: { self: { href: "http://scm/api/rest/v2/repositories/foo/bar" @@ -63,7 +65,7 @@ describe("changesets", () => { const expectedActions = [ { type: FETCH_CHANGESETS_PENDING, - payload: { repository, branch: "" }, + payload: { repository }, itemId: "foo/bar" }, { @@ -111,7 +113,7 @@ describe("changesets", () => { const expectedActions = [ { type: FETCH_CHANGESETS_PENDING, - payload: { repository, branch: "" }, + payload: { repository }, itemId } ]; @@ -152,7 +154,7 @@ describe("changesets", () => { const expectedActions = [ { type: FETCH_CHANGESETS_PENDING, - payload: { repository, branch: "" }, + payload: { repository }, itemId: "foo/bar" }, { From 1606add59a9ee5e010087aecaa1e6eddb57eb788 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Tue, 9 Oct 2018 09:22:06 +0200 Subject: [PATCH 16/52] Show BranchChooser div only when there are branches to choose from --- scm-ui/src/repos/containers/BranchChooser.js | 18 +++++++++++------- scm-ui/src/repos/containers/Changesets.js | 16 ++++++---------- scm-ui/src/repos/modules/branches.js | 4 ++-- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/scm-ui/src/repos/containers/BranchChooser.js b/scm-ui/src/repos/containers/BranchChooser.js index 607d96882c..007060acf1 100644 --- a/scm-ui/src/repos/containers/BranchChooser.js +++ b/scm-ui/src/repos/containers/BranchChooser.js @@ -18,7 +18,8 @@ type Props = { callback: (?Branch) => void, branches: Branch[], selectedBranchName: string, - loading: boolean + loading: boolean, + label: string }; type State = { @@ -39,17 +40,20 @@ class BranchChooser extends React.Component { } render() { - const { branches, loading } = this.props; + const { branches, loading, label } = this.props; if (loading) { return ; } if (branches && branches.length > 0) { return ( - b.name)} - preselectedOption={this.state.selectedBranchName} - optionSelected={branch => this.branchChanged(branch)} - /> +
+ + b.name)} + preselectedOption={this.state.selectedBranchName} + optionSelected={branch => this.branchChanged(branch)} + /> +
); } diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index 7e7dcc33f3..23841ffe96 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -144,16 +144,12 @@ class Changesets extends React.PureComponent { return ( <> -
- - this.branchChanged(branch)} - /> -
+ this.branchChanged(branch)} + /> ); diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index 6d70ae0925..c771a9734e 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -132,14 +132,14 @@ export function getBranch( state: Object, repository: Repository, name: string -): ?Branch { +): Branch { const key = createKey(repository); if (state.branches[key]) { if (state.branches[key].byNames[name]) { return state.branches[key].byNames[name]; } } - return undefined; + return null; } export function isFetchBranchesPending( From 6475320e616e0e08f28aac98544db881c660ad37 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Tue, 9 Oct 2018 11:52:16 +0200 Subject: [PATCH 17/52] Fixed unit test --- scm-ui/src/repos/modules/branches.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-ui/src/repos/modules/branches.test.js b/scm-ui/src/repos/modules/branches.test.js index cfae7da564..d9ade07d40 100644 --- a/scm-ui/src/repos/modules/branches.test.js +++ b/scm-ui/src/repos/modules/branches.test.js @@ -171,7 +171,7 @@ describe("branches", () => { it("should return undefined if branch does not exist", () => { const branch = getBranch(state, repository, "branch42"); - expect(branch).toBeUndefined(); + expect(branch).toBeFalsy(); }); }); }); From f95ffe25f30582f6759588183aa10f0d4f4610d7 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Tue, 9 Oct 2018 11:53:06 +0200 Subject: [PATCH 18/52] Fixes NavLink highlighting --- .../packages/ui-components/src/navigation/NavLink.js | 11 +++++++++-- scm-ui/src/repos/containers/RepositoryRoot.js | 7 +++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/navigation/NavLink.js b/scm-ui-components/packages/ui-components/src/navigation/NavLink.js index 3a90fd1fc6..20a7f2469f 100644 --- a/scm-ui-components/packages/ui-components/src/navigation/NavLink.js +++ b/scm-ui-components/packages/ui-components/src/navigation/NavLink.js @@ -7,7 +7,8 @@ import { Route, Link } from "react-router-dom"; type Props = { to: string, label: string, - activeOnlyWhenExact?: boolean + activeOnlyWhenExact?: boolean, + activeWhenMatch?: (route: any) => boolean }; class NavLink extends React.Component { @@ -15,11 +16,17 @@ class NavLink extends React.Component { activeOnlyWhenExact: true }; + + isActive(route: any) { + const { activeWhenMatch } = this.props; + return route.match || (activeWhenMatch && activeWhenMatch(route)); + } + renderLink = (route: any) => { const { to, label } = this.props; return (
  • - + {label}
  • diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index ec780a5bda..e9acf80a5d 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -70,6 +70,12 @@ class RepositoryRoot extends React.Component { this.props.deleteRepo(repository, this.deleted); }; + matches = (route: any) => { + const url = this.matchedUrl(); + const regex = new RegExp(`${url}/?[a-zA-Z0-9_%]*/changesets?.*`); + return route.location.pathname.match(regex); + }; + render() { const { loading, error, repository, t } = this.props; @@ -131,6 +137,7 @@ class RepositoryRoot extends React.Component { activeOnlyWhenExact={false} to={`${url}/changesets`} label={t("repository-root.history")} + activeWhenMatch={this.matches} /> From 3caac90d3c0f166d2f73cb0b257af5da12de3c73 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Tue, 9 Oct 2018 16:24:19 +0200 Subject: [PATCH 19/52] Fixed minor issues --- scm-ui/src/repos/containers/Changesets.js | 18 ++++++++---------- scm-ui/src/repos/modules/branches.js | 2 +- scm-ui/src/repos/modules/changesets.js | 3 +-- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index 23841ffe96..afdadc5d7d 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -13,6 +13,7 @@ import { fetchChangesetsByBranchAndPage, fetchChangesetsByLink, fetchChangesetsByPage, + getChangesets, getChangesetsFromState, getFetchChangesetsFailure, isFetchChangesetsPending, @@ -54,16 +55,11 @@ type Props = { branch: Branch }; -type State = { - branch: string -}; +type State = {}; class Changesets extends React.PureComponent { constructor(props: Props) { super(props); - this.state = { - branch: "" - }; } onPageChange = (link: string) => { @@ -95,16 +91,18 @@ class Changesets extends React.PureComponent { componentDidUpdate(prevProps: Props) { const { page, list, repository, match } = this.props; const { namespace, name } = repository; - const branch = decodeURIComponent(match.params.branch); + const branch = match.params.branch; + if (!this.props.loading) { if (prevProps.branch !== this.props.branch) { - this.setState({ 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( @@ -194,9 +192,9 @@ const getPageFromProps = props => { const mapStateToProps = (state, ownProps: Props) => { const { repository } = ownProps; const branchName = ownProps.match.params.branch; - const branch = getBranch(state, repository, branchName); + const branch = getBranch(state, repository, decodeURIComponent(branchName)); const loading = isFetchChangesetsPending(state, repository, branch); - const changesets = getChangesetsFromState(state, repository); + const changesets = getChangesets(state, repository, branch); const branchNames = getBranchNames(state, repository); const error = getFetchChangesetsFailure(state, repository, branch); const list = selectListAsCollection(state, repository); diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index c771a9734e..4b4297f4c5 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -132,7 +132,7 @@ export function getBranch( state: Object, repository: Repository, name: string -): Branch { +): ?Branch { const key = createKey(repository); if (state.branches[key]) { if (state.branches[key].byNames[name]) { diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index e7e7601196..6770e2257c 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -82,7 +82,6 @@ export function fetchChangesetsByPage(repository: Repository, page: number) { return fetchChangesetsWithOptions(repository, undefined, `?page=${page - 1}`); } -// TODO: Rewrite code to fetch changesets by branches, adjust tests and let BranchChooser fetch branches export function fetchChangesetsByBranchAndPage( repository: Repository, branch: Branch, @@ -171,7 +170,7 @@ function byKeyReducer( return { ...state, [key]: { - byId: { ...byIds }, + byId: byIds, list: { entries: changesetIds, entry: { From 62abae43688d3b80432b33f68772bdd16236db87 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Tue, 9 Oct 2018 21:36:41 +0200 Subject: [PATCH 20/52] Bootstrapped reimplementation of Changeset list --- .../src/repos/containers/BranchChangesets.js | 106 ++++++++++++++++++ .../repos/containers/ChangesetContainer.js | 61 ++++++++++ scm-ui/src/repos/containers/RepositoryRoot.js | 52 ++++----- scm-ui/src/repos/modules/branches.js | 14 +-- 4 files changed, 196 insertions(+), 37 deletions(-) create mode 100644 scm-ui/src/repos/containers/BranchChangesets.js create mode 100644 scm-ui/src/repos/containers/ChangesetContainer.js diff --git a/scm-ui/src/repos/containers/BranchChangesets.js b/scm-ui/src/repos/containers/BranchChangesets.js new file mode 100644 index 0000000000..982c500eab --- /dev/null +++ b/scm-ui/src/repos/containers/BranchChangesets.js @@ -0,0 +1,106 @@ +// @flow +import React from "react"; +import type {Branch, Repository} from "@scm-manager/ui-types"; +import {connect} from "react-redux"; +import {fetchBranches, getBranch, getBranchNames, isFetchBranchesPending} from "../modules/branches"; +import DropDown from "../components/DropDown"; +import type {History} from "history"; +import {withRouter} from "react-router-dom"; +import ChangesetContainer from "./ChangesetContainer"; +import {Loading} from "@scm-manager/ui-components"; + +type Props = { + repository: Repository, + branches: Branch[], + branchNames: string[], + fetchBranches: Repository => void, + history: History, + match: any, + selectedBranch: Branch, + label: string, //TODO: Should this be here? + loading: boolean +}; +type State = {}; + +class BranchChangesets extends React.Component { + componentDidMount() { + this.props.fetchBranches(this.props.repository); + } + + render() { + const { + repository, + branchNames, + match, + selectedBranch, + label, + loading + } = this.props; + const selectedBranchName = match.params.branch; + + if (loading) { + return ; + } + + // TODO: Handle errors + + if (!branchNames || branchNames.length === 0) { + return null; + } + + return ( + <> +
    + + this.branchSelected(branch)} + /> +
    + + + ); + } + + //TODO: Maybe extract this and let it be passed from parent component + branchSelected = (branchName: string) => { + const { namespace, name } = this.props.repository; + if (branchName === "") { + this.props.history.push(`/repo/${namespace}/${name}/changesets`); + } else { + this.props.history.push( + `/repo/${namespace}/${name}/branches/${branchName}/changesets` + ); + } + }; +} + +const mapDispatchToProps = dispatch => { + return { + fetchBranches: (repo: Repository) => { + dispatch(fetchBranches(repo)); + } + }; +}; + +const mapStateToProps = (state: any, ownProps: Props) => { + const loading = isFetchBranchesPending(state, ownProps.repository); + const branchNames = getBranchNames(state, ownProps.repository); + const selectedBranch = getBranch( + state, + ownProps.repository, + ownProps.match.params.branch //TODO: Maybe let parent component pass selected branch + ); + return { + loading, + branchNames, + selectedBranch + }; +}; +export default withRouter( + connect( + mapStateToProps, + mapDispatchToProps + )(BranchChangesets) +); diff --git a/scm-ui/src/repos/containers/ChangesetContainer.js b/scm-ui/src/repos/containers/ChangesetContainer.js new file mode 100644 index 0000000000..d6a473431d --- /dev/null +++ b/scm-ui/src/repos/containers/ChangesetContainer.js @@ -0,0 +1,61 @@ +// @flow + +import React from "react"; +import { withRouter } from "react-router-dom"; +import type { Branch, Changeset, Repository } from "@scm-manager/ui-types"; +import { + fetchChangesetsByBranch, + getChangesets, + isFetchChangesetsPending +} from "../modules/changesets"; +import { connect } from "react-redux"; +import ChangesetList from "../components/changesets/ChangesetList"; +import { Loading } from "@scm-manager/ui-components"; + +type Props = { + fetchChangesetsByBranch: (Repository, Branch) => void, + repository: Repository, //TODO: Do we really need/want this here? + branch: Branch, + changesets: Changeset[], + loading: boolean +}; +type State = {}; + +class ChangesetContainer extends React.Component { + componentDidMount() { + const { fetchChangesetsByBranch, repository, branch } = this.props; + fetchChangesetsByBranch(repository, branch); //TODO: fetch by page + } + + render() { + const { repository, changesets, loading } = this.props; + if (loading) { + return ; + } + if (!changesets || changesets.length === 0) { + return null; + } + return ; + } +} + +const mapDispatchToProps = dispatch => { + return { + fetchChangesetsByBranch: (repo: Repository, branch: Branch) => { + dispatch(fetchChangesetsByBranch(repo, branch)); + } + }; +}; + +const mapStateToProps = (state: any, ownProps: Props) => { + const { repository, branch } = ownProps; + const changesets = getChangesets(state, repository, branch); + const loading = isFetchChangesetsPending(state, repository, branch); + return { changesets, loading }; +}; +export default withRouter( + connect( + mapStateToProps, + mapDispatchToProps + )(ChangesetContainer) +); diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index e9acf80a5d..e3a78d8f34 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -1,31 +1,18 @@ //@flow import React from "react"; -import { - deleteRepo, - fetchRepo, - getFetchRepoFailure, - getRepository, - isFetchRepoPending -} from "../modules/repos"; -import { connect } from "react-redux"; -import { Route } from "react-router-dom"; -import type { Repository } from "@scm-manager/ui-types"; -import { - Page, - Loading, - ErrorPage, - Navigation, - NavLink, - Section -} from "@scm-manager/ui-components"; -import { translate } from "react-i18next"; +import {deleteRepo, fetchRepo, getFetchRepoFailure, getRepository, isFetchRepoPending} from "../modules/repos"; +import {connect} from "react-redux"; +import {Route} from "react-router-dom"; +import type {Repository} from "@scm-manager/ui-types"; +import {ErrorPage, Loading, Navigation, NavLink, Page, Section} from "@scm-manager/ui-components"; +import {translate} from "react-i18next"; import RepositoryDetails from "../components/RepositoryDetails"; import DeleteNavAction from "../components/DeleteNavAction"; import Edit from "../containers/Edit"; -import type { History } from "history"; +import type {History} from "history"; import EditNavLink from "../components/EditNavLink"; -import Changesets from "./Changesets"; +import BranchChangesets from "./BranchChangesets"; type Props = { namespace: string, @@ -94,7 +81,7 @@ class RepositoryRoot extends React.Component { } const url = this.matchedUrl(); - + // TODO: Changesets need to be adjusted (i.e. sub-routes need to be handled in sub-components) return (
    @@ -108,25 +95,34 @@ class RepositoryRoot extends React.Component { path={`${url}/edit`} component={() => } /> + } + render={() => ( + + )} /> } + render={() => ( + + )} /> } + path={`${url}/branches/:branch/changesets`} + render={() => ( + + )} /> } + path={`${url}/branches/:branch/changesets/:page`} + render={() => ( + + )} />
    diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index c771a9734e..6e0ca52d5c 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -1,12 +1,8 @@ // @flow -import { - FAILURE_SUFFIX, - PENDING_SUFFIX, - SUCCESS_SUFFIX -} from "../../modules/types"; -import { apiClient } from "@scm-manager/ui-components"; -import type { Repository, Action, Branch } from "@scm-manager/ui-types"; -import { isPending } from "../../modules/pending"; +import {FAILURE_SUFFIX, PENDING_SUFFIX, SUCCESS_SUFFIX} 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"; export const FETCH_BRANCHES = "scm/repos/FETCH_BRANCHES"; export const FETCH_BRANCHES_PENDING = `${FETCH_BRANCHES}_${PENDING_SUFFIX}`; @@ -113,7 +109,7 @@ export function getBranchNames( repository: Repository ): ?Array { const key = createKey(repository); - if (!state.branches[key] || !state.branches[key].byNames) { + if (!state.branches || !state.branches[key] || !state.branches[key].byNames) { return []; } return Object.keys(state.branches[key].byNames); From 795d0d8015a1f1627fa6471669b0ee6c7fe49a9a Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Wed, 10 Oct 2018 19:26:29 +0200 Subject: [PATCH 21/52] Implemented LinkPaginator, refactored code for changeset list --- .../ui-components/src/LinkPaginator.js | 129 ++++++++++ .../ui-components/src/buttons/Button.js | 4 +- .../packages/ui-components/src/index.js | 1 + .../src/repos/containers/BranchChangesets.js | 106 -------- scm-ui/src/repos/containers/BranchChooser.js | 157 +++++++----- .../repos/containers/ChangesetContainer.js | 61 ----- scm-ui/src/repos/containers/Changesets.js | 240 ++++-------------- scm-ui/src/repos/containers/RepositoryRoot.js | 55 ++-- scm-ui/src/repos/modules/branches.js | 19 +- scm-ui/src/repos/modules/branches.test.js | 16 ++ scm-ui/src/repos/modules/changesets.js | 50 ++-- scm-ui/src/repos/modules/changesets.test.js | 43 +--- 12 files changed, 374 insertions(+), 507 deletions(-) create mode 100644 scm-ui-components/packages/ui-components/src/LinkPaginator.js delete mode 100644 scm-ui/src/repos/containers/BranchChangesets.js delete mode 100644 scm-ui/src/repos/containers/ChangesetContainer.js diff --git a/scm-ui-components/packages/ui-components/src/LinkPaginator.js b/scm-ui-components/packages/ui-components/src/LinkPaginator.js new file mode 100644 index 0000000000..75e53df62a --- /dev/null +++ b/scm-ui-components/packages/ui-components/src/LinkPaginator.js @@ -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 { + renderFirstButton() { + return ( +
    @@ -131,7 +138,7 @@ class RepositoryRoot extends React.Component { diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index 6e0ca52d5c..2a4580e194 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -1,8 +1,13 @@ // @flow -import {FAILURE_SUFFIX, PENDING_SUFFIX, SUCCESS_SUFFIX} 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 { + FAILURE_SUFFIX, + PENDING_SUFFIX, + SUCCESS_SUFFIX +} 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"; export const FETCH_BRANCHES = "scm/repos/FETCH_BRANCHES"; export const FETCH_BRANCHES_PENDING = `${FETCH_BRANCHES}_${PENDING_SUFFIX}`; @@ -128,7 +133,7 @@ export function getBranch( state: Object, repository: Repository, name: string -): Branch { +): ?Branch { const key = createKey(repository); if (state.branches[key]) { if (state.branches[key].byNames[name]) { @@ -145,6 +150,10 @@ export function isFetchBranchesPending( 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}`; diff --git a/scm-ui/src/repos/modules/branches.test.js b/scm-ui/src/repos/modules/branches.test.js index d9ade07d40..c958f105ef 100644 --- a/scm-ui/src/repos/modules/branches.test.js +++ b/scm-ui/src/repos/modules/branches.test.js @@ -10,6 +10,7 @@ import reducer, { getBranch, getBranches, getBranchNames, + getFetchBranchesFailure, isFetchBranchesPending } from "./branches"; @@ -129,6 +130,8 @@ describe("branches", () => { }); describe("branch selectors", () => { + const error = new Error("Something went wrong"); + const state = { branches: { [key]: { @@ -173,5 +176,18 @@ describe("branches", () => { const branch = getBranch(state, repository, "branch42"); 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(); + }); }); }); diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index e7e7601196..191de7b37d 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -1,21 +1,11 @@ // @flow -import { - FAILURE_SUFFIX, - PENDING_SUFFIX, - SUCCESS_SUFFIX -} from "../../modules/types"; -import { apiClient } from "@scm-manager/ui-components"; -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"; +import {FAILURE_SUFFIX, PENDING_SUFFIX, SUCCESS_SUFFIX} from "../../modules/types"; +import {apiClient} from "@scm-manager/ui-components"; +import {isPending} from "../../modules/pending"; +import {getFailure} from "../../modules/failure"; +import {combineReducers} from "redux"; +import type {Action, Branch, Changeset, PagedCollection, Repository} from "@scm-manager/ui-types"; export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS"; export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; @@ -82,7 +72,6 @@ export function fetchChangesetsByPage(repository: Repository, page: number) { return fetchChangesetsWithOptions(repository, undefined, `?page=${page - 1}`); } -// TODO: Rewrite code to fetch changesets by branches, adjust tests and let BranchChooser fetch branches export function fetchChangesetsByBranchAndPage( repository: Repository, branch: Branch, @@ -167,11 +156,11 @@ function byKeyReducer( if (state[key]) { oldChangesets[key] = state[key]; } - const byIds = extractChangesetsByIds(changesets, oldChangesets[key].byId); + const byIds = extractChangesetsByIds(changesets); return { ...state, [key]: { - byId: { ...byIds }, + byId: byIds, list: { entries: changesetIds, entry: { @@ -191,17 +180,13 @@ export default combineReducers({ byKey: byKeyReducer }); -function extractChangesetsByIds(changesets: any, oldChangesetsByIds: any) { +function extractChangesetsByIds(changesets: any) { const changesetsByIds = {}; for (let changeset of changesets) { changesetsByIds[changeset.id] = changeset; } - for (let id in oldChangesetsByIds) { - changesetsByIds[id] = oldChangesetsByIds[id]; - } - return changesetsByIds; } @@ -234,16 +219,20 @@ export function getFetchChangesetsFailure( return getFailure(state, FETCH_CHANGESETS, createItemId(repository, branch)); } -const selectList = (state: Object, repository: Repository) => { - const itemId = createItemId(repository); +const selectList = (state: Object, repository: Repository, branch?: Branch) => { + const itemId = createItemId(repository, branch); if (state.changesets.byKey[itemId] && state.changesets.byKey[itemId].list) { return state.changesets.byKey[itemId].list; } return {}; }; -const selectListEntry = (state: Object, repository: Repository): Object => { - const list = selectList(state, repository); +const selectListEntry = ( + state: Object, + repository: Repository, + branch?: Branch +): Object => { + const list = selectList(state, repository, branch); if (list.entry) { return list.entry; } @@ -252,9 +241,10 @@ const selectListEntry = (state: Object, repository: Repository): Object => { export const selectListAsCollection = ( state: Object, - repository: Repository + repository: Repository, + branch?: Branch ): PagedCollection => { - return selectListEntry(state, repository); + return selectListEntry(state, repository, branch); }; export function getChangesetsFromState(state: Object, repository: Repository) { diff --git a/scm-ui/src/repos/modules/changesets.test.js b/scm-ui/src/repos/modules/changesets.test.js index 6a557cf1ac..fd33efbc7b 100644 --- a/scm-ui/src/repos/modules/changesets.test.js +++ b/scm-ui/src/repos/modules/changesets.test.js @@ -3,21 +3,21 @@ import configureMockStore from "redux-mock-store"; import thunk from "redux-thunk"; import fetchMock from "fetch-mock"; -import { +import reducer, { FETCH_CHANGESETS, FETCH_CHANGESETS_FAILURE, FETCH_CHANGESETS_PENDING, FETCH_CHANGESETS_SUCCESS, fetchChangesets, - fetchChangesetsByBranchAndPage, fetchChangesetsByBranch, + fetchChangesetsByBranchAndPage, fetchChangesetsByPage, fetchChangesetsSuccess, getChangesets, getFetchChangesetsFailure, isFetchChangesetsPending } from "./changesets"; -import reducer from "./changesets"; + const branch = { name: "specific", revision: "123", @@ -40,7 +40,10 @@ const repository = { 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"] }); }); - - 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", () => { From 2de0287bf9cbc1fd6327b5a6bed4dec9f584e9fc Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Thu, 11 Oct 2018 17:29:50 +0200 Subject: [PATCH 22/52] Bootstrapped BranchRoot --- .../ui-components/src/LinkPaginator.js | 2 + .../ui-components/src/buttons/Button.js | 30 ++++---- scm-ui/public/locales/en/repos.json | 9 ++- scm-ui/src/repos/containers/BranchChooser.js | 77 ++++++++++++------- scm-ui/src/repos/containers/BranchRoot.js | 63 +++++++++++++++ scm-ui/src/repos/containers/Changesets.js | 13 ++-- scm-ui/src/repos/containers/RepositoryRoot.js | 54 +++++-------- 7 files changed, 163 insertions(+), 85 deletions(-) create mode 100644 scm-ui/src/repos/containers/BranchRoot.js diff --git a/scm-ui-components/packages/ui-components/src/LinkPaginator.js b/scm-ui-components/packages/ui-components/src/LinkPaginator.js index 75e53df62a..4e21033f93 100644 --- a/scm-ui-components/packages/ui-components/src/LinkPaginator.js +++ b/scm-ui-components/packages/ui-components/src/LinkPaginator.js @@ -12,6 +12,8 @@ type Props = { }; class LinkPaginator extends React.Component { + //TODO: HATEOAS-Links verwenden + renderFirstButton() { return (