From 115fa4fb52988584e2e97ebd483405e25ea1d380 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Mon, 10 Sep 2018 17:00:53 +0200 Subject: [PATCH 01/81] added changesets module --- scm-ui/src/changesets/modules/changesets.js | 71 +++++++++++++++++++ .../src/changesets/modules/changesets.test.js | 66 +++++++++++++++++ scm-ui/src/createReduxStore.js | 2 + scm-ui/src/repos/modules/repos.test.js | 2 +- 4 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 scm-ui/src/changesets/modules/changesets.js create mode 100644 scm-ui/src/changesets/modules/changesets.test.js diff --git a/scm-ui/src/changesets/modules/changesets.js b/scm-ui/src/changesets/modules/changesets.js new file mode 100644 index 0000000000..639a4ff1db --- /dev/null +++ b/scm-ui/src/changesets/modules/changesets.js @@ -0,0 +1,71 @@ +// @flow + +import {FAILURE_SUFFIX, PENDING_SUFFIX, SUCCESS_SUFFIX} from "../../modules/types"; +import { apiClient } from "@scm-manager/ui-components"; + +export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS"; +export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; +export const FETCH_CHANGESETS_SUCCESS = `${FETCH_CHANGESETS}_${SUCCESS_SUFFIX}`; +export const FETCH_CHANGESETS_FAILURE = `${FETCH_CHANGESETS}_${FAILURE_SUFFIX}`; + +const REPO_URL = "repositories"; + +export function fetchChangesets(namespace: string, name: string) { + return fetchChangesetsByLink(REPO_URL + "/" + namespace + "/" + name + "/changesets"); +} + +export function fetchChangesetsByLink(link: string) { + return function(dispatch: any) { + dispatch(fetchChangesetsPending()); + return apiClient.get(link).then(response => response.json()) + .then(data => { + dispatch(fetchChangesetsSuccess(data)) + }).catch(cause => { + dispatch(fetchChangesetsFailure(link, cause)) + }) + } +} + +export function fetchChangesetsPending(): Action { + return { + type: FETCH_CHANGESETS_PENDING + } +} + +export function fetchChangesetsSuccess(data: any): Action { + return { + type: FETCH_CHANGESETS_SUCCESS, + payload: data + } +} + +function fetchChangesetsFailure(url: string, error: Error): Action { + return { + type: FETCH_CHANGESETS_FAILURE, + payload: { + url, + error + } + } +} + + +export default function reducer(state: any = {}, action: any = {}) { + switch (action.type) { + case FETCH_CHANGESETS_SUCCESS: + + return {byIds: extractChangesetsByIds(action.payload)}; + default: + return state; + } +} + +function extractChangesetsByIds(data: any) { + const changesets = data._embedded.changesets; + const changesetsByIds = {}; + for (let changeset of changesets) { + changesetsByIds[changeset.id] = changeset; + } + + return changesetsByIds; +} diff --git a/scm-ui/src/changesets/modules/changesets.test.js b/scm-ui/src/changesets/modules/changesets.test.js new file mode 100644 index 0000000000..aaeb75451e --- /dev/null +++ b/scm-ui/src/changesets/modules/changesets.test.js @@ -0,0 +1,66 @@ +// @flow + + +import configureMockStore from "redux-mock-store"; +import thunk from "redux-thunk"; +import fetchMock from "fetch-mock"; +import { + FETCH_CHANGESETS_PENDING, + FETCH_CHANGESETS_SUCCESS, + fetchChangesets, + fetchChangesetsSuccess +} from "./changesets"; +import reducer from "./changesets"; + +const collection = {}; + +describe("fetching of changesets", () => { + const URL = "/api/rest/v2/repositories/foo/bar/changesets"; + const mockStore = configureMockStore([thunk]); + + afterEach(() => { + fetchMock.reset(); + fetchMock.restore(); + }); + + it("should fetch changesets", () => { + fetchMock.getOnce(URL, "{}"); + + const expectedActions = [ + { type: FETCH_CHANGESETS_PENDING }, + { + type: FETCH_CHANGESETS_SUCCESS, + payload: collection + } + ]; + + const store = mockStore({}); + return store.dispatch(fetchChangesets("foo", "bar")).then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); + }) +}); + +describe("changesets reducer", () => { + const responseBody = { + _embedded: { + changesets: [ + {id: "changeset1", author: { mail: "z@phod.com", name: "zaphod"}}, + {id: "changeset2"}, + {id: "changeset3"}, + ], + _embedded: { + tags: [], + branches: [], + parents: [] + } + } + }; + it("should set state correctly", () => { + const newState = reducer({}, fetchChangesetsSuccess(responseBody)); + expect(newState.byIds["changeset1"]).toBeDefined(); + expect(newState.byIds["changeset1"].author.mail).toEqual("z@phod.com"); + expect(newState.byIds["changeset2"]).toBeDefined(); + expect(newState.byIds["changeset3"]).toBeDefined(); + }) +}); diff --git a/scm-ui/src/createReduxStore.js b/scm-ui/src/createReduxStore.js index 8411326b53..a63a6edcdb 100644 --- a/scm-ui/src/createReduxStore.js +++ b/scm-ui/src/createReduxStore.js @@ -7,6 +7,7 @@ import { routerReducer, routerMiddleware } from "react-router-redux"; import users from "./users/modules/users"; import repos from "./repos/modules/repos"; import repositoryTypes from "./repos/modules/repositoryTypes"; +import changesets from "./changesets/modules/changesets"; import groups from "./groups/modules/groups"; import auth from "./modules/auth"; import pending from "./modules/pending"; @@ -26,6 +27,7 @@ function createReduxStore(history: BrowserHistory) { users, repos, repositoryTypes, + changesets, groups, auth, config diff --git a/scm-ui/src/repos/modules/repos.test.js b/scm-ui/src/repos/modules/repos.test.js index ae85d6cb17..d537f4bdc0 100644 --- a/scm-ui/src/repos/modules/repos.test.js +++ b/scm-ui/src/repos/modules/repos.test.js @@ -405,7 +405,7 @@ describe("repos fetch", () => { }); }); - it("should disapatch failure if server returns status code 500", () => { + it("should dispatch failure if server returns status code 500", () => { fetchMock.postOnce(REPOS_URL, { status: 500 }); From 9e489cfd1da6b9ee7a0c039f0c99ec9887ad498c Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Tue, 11 Sep 2018 17:20:30 +0200 Subject: [PATCH 02/81] Added types, components and logic for changesets --- .../packages/ui-types/src/Changesets.js | 18 +++++ .../packages/ui-types/src/Tags.js | 8 ++ .../packages/ui-types/src/index.js | 4 + scm-ui/public/locales/en/changesets.json | 12 +++ .../src/changesets/components/ChangesetRow.js | 21 ++++++ .../src/changesets/components/Changesets.js | 59 +++++++++++++++ scm-ui/src/changesets/modules/changesets.js | 59 ++++++++++----- .../src/changesets/modules/changesets.test.js | 74 +++++++++++++++---- .../src/repos/components/RepositoryDetails.js | 2 + 9 files changed, 224 insertions(+), 33 deletions(-) create mode 100644 scm-ui-components/packages/ui-types/src/Changesets.js create mode 100644 scm-ui-components/packages/ui-types/src/Tags.js create mode 100644 scm-ui/public/locales/en/changesets.json create mode 100644 scm-ui/src/changesets/components/ChangesetRow.js create mode 100644 scm-ui/src/changesets/components/Changesets.js diff --git a/scm-ui-components/packages/ui-types/src/Changesets.js b/scm-ui-components/packages/ui-types/src/Changesets.js new file mode 100644 index 0000000000..9979cf2d89 --- /dev/null +++ b/scm-ui-components/packages/ui-types/src/Changesets.js @@ -0,0 +1,18 @@ +//@flow +import type { Links } from "./hal"; +import type { Tag } from "./Tags"; +export type Changeset = { + id: string, + date: Date, + author: { + name: string, + mail: string + }, + description: string + _links: Links, + _embedded: { + tags: Tag[] + branches: any, //todo: Add correct type + parents: any //todo: Add correct type + }; +} diff --git a/scm-ui-components/packages/ui-types/src/Tags.js b/scm-ui-components/packages/ui-types/src/Tags.js new file mode 100644 index 0000000000..1e6244124a --- /dev/null +++ b/scm-ui-components/packages/ui-types/src/Tags.js @@ -0,0 +1,8 @@ +//@flow +import type { Links } from "./hal"; + +export type Tag = { + name: string, + revision: string, + _links: Links +} diff --git a/scm-ui-components/packages/ui-types/src/index.js b/scm-ui-components/packages/ui-types/src/index.js index 109fb9bb6a..5e9c14e758 100644 --- a/scm-ui-components/packages/ui-types/src/index.js +++ b/scm-ui-components/packages/ui-types/src/index.js @@ -9,4 +9,8 @@ export type { Group, Member } from "./Group"; export type { Repository, RepositoryCollection, RepositoryGroup } from "./Repositories"; export type { RepositoryType, RepositoryTypeCollection } from "./RepositoryTypes"; +export type { Changeset } from "./Changesets"; + +export type { Tag } from "./Tags" + export type { Config } from "./Config"; diff --git a/scm-ui/public/locales/en/changesets.json b/scm-ui/public/locales/en/changesets.json new file mode 100644 index 0000000000..6628fcf62f --- /dev/null +++ b/scm-ui/public/locales/en/changesets.json @@ -0,0 +1,12 @@ +{ + "changeset": { + "id": "ID", + "description": "Description", + "contact": "Contact", + "date": "Date" + }, + "author": { + "name": "Author", + "mail": "Mail" + } +} diff --git a/scm-ui/src/changesets/components/ChangesetRow.js b/scm-ui/src/changesets/components/ChangesetRow.js new file mode 100644 index 0000000000..afc544492a --- /dev/null +++ b/scm-ui/src/changesets/components/ChangesetRow.js @@ -0,0 +1,21 @@ +import React from "react" +import type { Changeset } from "@scm-manager/ui-types" + +type Props = { + changeset: Changeset +} + +class ChangesetRow extends React.Component { + + // Todo: Add extension point to author field + render() { + const {changeset} = this.props; + return + { changeset.author.name } + { changeset.description } + { changeset.date } + + } +} + +export default ChangesetRow; diff --git a/scm-ui/src/changesets/components/Changesets.js b/scm-ui/src/changesets/components/Changesets.js new file mode 100644 index 0000000000..733461b722 --- /dev/null +++ b/scm-ui/src/changesets/components/Changesets.js @@ -0,0 +1,59 @@ +import React from "react" +import { connect } from "react-redux"; +import ChangesetRow from "./ChangesetRow"; +import type {Changeset} from "@scm-manager/ui-types"; + +import { fetchChangesetsByNamespaceAndName, getChangesetsForNameAndNamespaceFromState } from "../modules/changesets"; +import { translate } from "react-i18next"; + +type Props = { + changesets: Changeset[], + t: string => string +} + +class Changesets extends React.Component { + componentDidMount() { + const {namespace, name} = this.props.repository; + this.props.fetchChangesetsByNamespaceAndName(namespace, name); + } + + render() { + const { t, changesets } = this.props; + if (!changesets) { + return null; + } + return + + + + + + + + + {changesets.map((changeset, index) => { + return ; + })} + +
{t("author.name")}{t("changeset.description")}{t("changeset.date")}
+ } +} + +const mapStateToProps = (state, ownProps) => { + return { + changesets: getChangesetsForNameAndNamespaceFromState(ownProps.repository.namespace, ownProps.repository.name, state) + } +}; + +const mapDispatchToProps = dispatch => { + return { + fetchChangesetsByNamespaceAndName: (namespace: string, name: string) => { + dispatch(fetchChangesetsByNamespaceAndName(namespace, name)) + } + } +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(translate("changesets")(Changesets)); diff --git a/scm-ui/src/changesets/modules/changesets.js b/scm-ui/src/changesets/modules/changesets.js index 639a4ff1db..78715b53cc 100644 --- a/scm-ui/src/changesets/modules/changesets.js +++ b/scm-ui/src/changesets/modules/changesets.js @@ -1,7 +1,7 @@ // @flow import {FAILURE_SUFFIX, PENDING_SUFFIX, SUCCESS_SUFFIX} from "../../modules/types"; -import { apiClient } from "@scm-manager/ui-components"; +import {apiClient} from "@scm-manager/ui-components"; export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS"; export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; @@ -10,62 +10,85 @@ export const FETCH_CHANGESETS_FAILURE = `${FETCH_CHANGESETS}_${FAILURE_SUFFIX}`; const REPO_URL = "repositories"; -export function fetchChangesets(namespace: string, name: string) { - return fetchChangesetsByLink(REPO_URL + "/" + namespace + "/" + name + "/changesets"); -} -export function fetchChangesetsByLink(link: string) { - return function(dispatch: any) { - dispatch(fetchChangesetsPending()); - return apiClient.get(link).then(response => response.json()) +// actions +export function fetchChangesetsByNamespaceAndName(namespace: string, name: string) { + return function (dispatch: any) { + dispatch(fetchChangesetsPending(namespace, name)); + return apiClient.get(REPO_URL + "/" + namespace + "/" + name + "/changesets").then(response => response.json()) .then(data => { - dispatch(fetchChangesetsSuccess(data)) + dispatch(fetchChangesetsSuccess(data, namespace, name)) }).catch(cause => { dispatch(fetchChangesetsFailure(link, cause)) }) } } -export function fetchChangesetsPending(): Action { +export function fetchChangesetsPending(namespace: string, name: string): Action { return { - type: FETCH_CHANGESETS_PENDING + type: FETCH_CHANGESETS_PENDING, + payload: { + namespace, + name + } } } -export function fetchChangesetsSuccess(data: any): Action { +export function fetchChangesetsSuccess(collection: any, namespace: string, name: string): Action { return { type: FETCH_CHANGESETS_SUCCESS, - payload: data + payload: {collection, namespace, name} } } -function fetchChangesetsFailure(url: string, error: Error): Action { +function fetchChangesetsFailure(namespace: string, name: string, error: Error): Action { return { type: FETCH_CHANGESETS_FAILURE, payload: { - url, + namespace, + name, error } } } - +// reducer export default function reducer(state: any = {}, action: any = {}) { switch (action.type) { case FETCH_CHANGESETS_SUCCESS: + const {namespace, name} = action.payload; + const key = namespace + "/" + name; - return {byIds: extractChangesetsByIds(action.payload)}; + let oldChangesets = {[key]: {}}; + if (state[key] !== undefined) { + oldChangesets[key] = state[key] + } + return {[key]: {byId: extractChangesetsByIds(action.payload.collection, oldChangesets[key].byId)}}; default: return state; } } -function extractChangesetsByIds(data: any) { +function extractChangesetsByIds(data: any, oldChangesetsByIds: any) { const changesets = data._embedded.changesets; const changesetsByIds = {}; + for (let changeset of changesets) { changesetsByIds[changeset.id] = changeset; } + for (let id in oldChangesetsByIds) { + changesetsByIds[id] = oldChangesetsByIds[id]; + } + return changesetsByIds; } + +//selectors +export function getChangesetsForNameAndNamespaceFromState(namespace: string, name: string, state: any) { + const key = namespace + "/" + name; + if (!state.changesets[key]) { + return null; + } + return Object.values(state.changesets[key].byId); +} diff --git a/scm-ui/src/changesets/modules/changesets.test.js b/scm-ui/src/changesets/modules/changesets.test.js index aaeb75451e..7191846ca2 100644 --- a/scm-ui/src/changesets/modules/changesets.test.js +++ b/scm-ui/src/changesets/modules/changesets.test.js @@ -1,14 +1,13 @@ // @flow - import configureMockStore from "redux-mock-store"; import thunk from "redux-thunk"; import fetchMock from "fetch-mock"; import { FETCH_CHANGESETS_PENDING, FETCH_CHANGESETS_SUCCESS, - fetchChangesets, - fetchChangesetsSuccess + fetchChangesetsByNamespaceAndName, + fetchChangesetsSuccess, getChangesetsForNameAndNamespaceFromState } from "./changesets"; import reducer from "./changesets"; @@ -27,15 +26,15 @@ describe("fetching of changesets", () => { fetchMock.getOnce(URL, "{}"); const expectedActions = [ - { type: FETCH_CHANGESETS_PENDING }, + {type: FETCH_CHANGESETS_PENDING, payload: {namespace: "foo", name: "bar"}}, { type: FETCH_CHANGESETS_SUCCESS, - payload: collection + payload: {collection, namespace: "foo", name: "bar"} } ]; const store = mockStore({}); - return store.dispatch(fetchChangesets("foo", "bar")).then(() => { + return store.dispatch(fetchChangesetsByNamespaceAndName("foo", "bar")).then(() => { expect(store.getActions()).toEqual(expectedActions); }); }) @@ -45,9 +44,9 @@ describe("changesets reducer", () => { const responseBody = { _embedded: { changesets: [ - {id: "changeset1", author: { mail: "z@phod.com", name: "zaphod"}}, - {id: "changeset2"}, - {id: "changeset3"}, + {id: "changeset1", author: {mail: "z@phod.com", name: "zaphod"}}, + {id: "changeset2", description: "foo"}, + {id: "changeset3", description: "bar"}, ], _embedded: { tags: [], @@ -56,11 +55,56 @@ describe("changesets reducer", () => { } } }; - it("should set state correctly", () => { - const newState = reducer({}, fetchChangesetsSuccess(responseBody)); - expect(newState.byIds["changeset1"]).toBeDefined(); - expect(newState.byIds["changeset1"].author.mail).toEqual("z@phod.com"); - expect(newState.byIds["changeset2"]).toBeDefined(); - expect(newState.byIds["changeset3"]).toBeDefined(); + + it("should set state to received changesets", () => { + const newState = reducer({}, fetchChangesetsSuccess(responseBody, "foo", "bar")); + expect(newState).toBeDefined(); + expect(newState["foo/bar"].byId["changeset1"].author.mail).toEqual("z@phod.com"); + expect(newState["foo/bar"].byId["changeset2"].description).toEqual("foo"); + expect(newState["foo/bar"].byId["changeset3"].description).toEqual("bar"); + }); + + 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({ + "foo/bar": { + byId: { + ["changeset2"]: { + id: "changeset2", + author: {mail: "mail@author.com", name: "author"} + } + } + } + }, fetchChangesetsSuccess(responseBody, "foo", "bar")); + expect(newState["foo/bar"].byId["changeset2"]).toBeDefined(); + expect(newState["foo/bar"].byId["changeset1"]).toBeDefined(); + }) +}); + +describe("changeset selectors", () => { + it("should get all changesets for a given namespace and name", () => { + const state = { + changesets: { + ["foo/bar"]: { + byId: { + "id1": {id: "id1"}, + "id2": {id: "id2"} + } + } + } + }; + const result = getChangesetsForNameAndNamespaceFromState("foo", "bar", state); + expect(result).toContainEqual({id: "id1"}) }) }); diff --git a/scm-ui/src/repos/components/RepositoryDetails.js b/scm-ui/src/repos/components/RepositoryDetails.js index 99c88fec94..33edaff541 100644 --- a/scm-ui/src/repos/components/RepositoryDetails.js +++ b/scm-ui/src/repos/components/RepositoryDetails.js @@ -3,6 +3,7 @@ import React from "react"; import type { Repository } from "@scm-manager/ui-types"; import RepositoryDetailTable from "./RepositoryDetailTable"; import { ExtensionPoint } from "@scm-manager/ui-extensions"; +import Changesets from "../../changesets/components/Changesets"; type Props = { repository: Repository @@ -20,6 +21,7 @@ class RepositoryDetails extends React.Component { renderAll={true} props={{ repository }} /> + ); From ee87ada415068627e4205a43f617e9aed0faf796 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Wed, 12 Sep 2018 17:15:16 +0200 Subject: [PATCH 03/81] Added branches module --- scm-ui/src/repos/modules/branches.js | 53 +++++++++++++++++++ scm-ui/src/repos/modules/branches.test.js | 63 +++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 scm-ui/src/repos/modules/branches.js create mode 100644 scm-ui/src/repos/modules/branches.test.js diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js new file mode 100644 index 0000000000..dde5cb379b --- /dev/null +++ b/scm-ui/src/repos/modules/branches.js @@ -0,0 +1,53 @@ +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}`; +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) { + 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(cause => { + dispatch(fetchBranchesFailure(namespace, name, cause)) + }) + } +} + +// Action creators +export function fetchBranchesPending(namespace: string, name: string) { + return { + type: FETCH_BRANCHES_PENDING, + payload: {namespace, name}, + itemId: namespace + "/" + name + } +} + +export function fetchBranchesSuccess(data: string, namespace: string, name: string) { + return { + type: FETCH_BRANCHES_SUCCESS, + payload: {data, namespace, name}, + itemId: namespace + "/" + name + } +} + +export function fetchBranchesFailure(namespace: string, name: string, error: Error) { + return { + type: FETCH_BRANCHES_FAILURE, + payload: {error, namespace, name}, + itemId: namespace + "/" + name + } +} + +// Reducers + +// Selectors diff --git a/scm-ui/src/repos/modules/branches.test.js b/scm-ui/src/repos/modules/branches.test.js new file mode 100644 index 0000000000..67c5950c5a --- /dev/null +++ b/scm-ui/src/repos/modules/branches.test.js @@ -0,0 +1,63 @@ +import configureMockStore from "redux-mock-store"; +import thunk from "redux-thunk"; +import fetchMock from "fetch-mock"; +import { + FETCH_BRANCHES_FAILURE, + FETCH_BRANCHES_PENDING, + FETCH_BRANCHES_SUCCESS, + fetchBranchesByNamespaceAndName +} from "./branches"; + +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 = {}; + + fetchMock.getOnce(URL, "{}"); + + const expectedActions = [ + {type: FETCH_BRANCHES_PENDING, payload: {namespace: "foo", name: "bar"}, + itemId: "foo/bar"}, + { + type: FETCH_BRANCHES_SUCCESS, + payload: {data: collection, namespace: "foo", name: "bar"}, + itemId: "foo/bar" + } + ]; + + const store = mockStore({}); + return store.dispatch(fetchBranchesByNamespaceAndName("foo", "bar")).then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); + }); + + it("should fail fetching branches on HTTP 500", () => { + const collection = {}; + + fetchMock.getOnce(URL, 500); + + const expectedActions = [ + {type: FETCH_BRANCHES_PENDING, payload: {namespace: "foo", name: "bar"}, + itemId: "foo/bar"}, + { + type: FETCH_BRANCHES_FAILURE, + payload: {error: collection, namespace: "foo", name: "bar"}, + itemId: "foo/bar" + } + ]; + + const store = mockStore({}); + return store.dispatch(fetchBranchesByNamespaceAndName("foo", "bar")).then(() => { + expect(store.getActions()[0]).toEqual(expectedActions[0]); + expect(store.getActions()[1].type).toEqual(FETCH_BRANCHES_FAILURE); + }); + }) +}); From 75002ffcff20860cf1d0a5451033d62c921c2752 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Wed, 12 Sep 2018 17:15:58 +0200 Subject: [PATCH 04/81] Added pending/error-logic for changesets Styled the changeset table a bit --- .../src/changesets/components/ChangesetRow.js | 17 ++++-- .../src/changesets/components/Changesets.js | 8 +-- scm-ui/src/changesets/modules/changesets.js | 24 ++++++-- .../src/changesets/modules/changesets.test.js | 61 ++++++++++++++++++- 4 files changed, 91 insertions(+), 19 deletions(-) diff --git a/scm-ui/src/changesets/components/ChangesetRow.js b/scm-ui/src/changesets/components/ChangesetRow.js index afc544492a..d0be206866 100644 --- a/scm-ui/src/changesets/components/ChangesetRow.js +++ b/scm-ui/src/changesets/components/ChangesetRow.js @@ -1,5 +1,6 @@ import React from "react" import type { Changeset } from "@scm-manager/ui-types" +import {ExtensionPoint} from "@scm-manager/ui-extensions"; type Props = { changeset: Changeset @@ -7,13 +8,19 @@ type Props = { class ChangesetRow extends React.Component { - // Todo: Add extension point to author field render() { - const {changeset} = this.props; + const { changeset } = this.props; + // todo: i18n return - { changeset.author.name } - { changeset.description } - { changeset.date } + + +

{changeset.description}

+

Changeset { changeset.id } commited at { changeset.date }

+

{changeset.author.name} <{changeset.author.mail}>

} } diff --git a/scm-ui/src/changesets/components/Changesets.js b/scm-ui/src/changesets/components/Changesets.js index 733461b722..ea0ac7aad1 100644 --- a/scm-ui/src/changesets/components/Changesets.js +++ b/scm-ui/src/changesets/components/Changesets.js @@ -22,13 +22,9 @@ class Changesets extends React.Component { if (!changesets) { return null; } - return + return
- - - - - + Changesets {changesets.map((changeset, index) => { diff --git a/scm-ui/src/changesets/modules/changesets.js b/scm-ui/src/changesets/modules/changesets.js index 78715b53cc..1aaa77f961 100644 --- a/scm-ui/src/changesets/modules/changesets.js +++ b/scm-ui/src/changesets/modules/changesets.js @@ -2,6 +2,8 @@ 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"; export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS"; export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; @@ -19,7 +21,7 @@ export function fetchChangesetsByNamespaceAndName(namespace: string, name: strin .then(data => { dispatch(fetchChangesetsSuccess(data, namespace, name)) }).catch(cause => { - dispatch(fetchChangesetsFailure(link, cause)) + dispatch(fetchChangesetsFailure(namespace, name, cause)) }) } } @@ -30,14 +32,16 @@ export function fetchChangesetsPending(namespace: string, name: string): Action payload: { namespace, name - } + }, + itemId: namespace + "/" + name } } export function fetchChangesetsSuccess(collection: any, namespace: string, name: string): Action { return { type: FETCH_CHANGESETS_SUCCESS, - payload: {collection, namespace, name} + payload: {collection, namespace, name}, + itemId: namespace + "/" + name } } @@ -48,7 +52,8 @@ function fetchChangesetsFailure(namespace: string, name: string, error: Error): namespace, name, error - } + }, + itemId: namespace + "/" + name } } @@ -85,10 +90,19 @@ function extractChangesetsByIds(data: any, oldChangesetsByIds: any) { } //selectors -export function getChangesetsForNameAndNamespaceFromState(namespace: string, name: string, state: any) { +export function getChangesetsForNameAndNamespaceFromState(namespace: string, name: string, state: Object) { const key = namespace + "/" + name; if (!state.changesets[key]) { return null; } return Object.values(state.changesets[key].byId); } + +export function isFetchChangesetsPending( state: Object, namespace: string, name: string) { + return isPending(state, FETCH_CHANGESETS, namespace + "/" + name) +} + +export function getFetchChangesetsFailure( state: Object, namespace: string, name: string) { + return getFailure(state, FETCH_CHANGESETS, namespace + "/" + name); +} + diff --git a/scm-ui/src/changesets/modules/changesets.test.js b/scm-ui/src/changesets/modules/changesets.test.js index 7191846ca2..c514978144 100644 --- a/scm-ui/src/changesets/modules/changesets.test.js +++ b/scm-ui/src/changesets/modules/changesets.test.js @@ -4,10 +4,11 @@ import configureMockStore from "redux-mock-store"; import thunk from "redux-thunk"; import fetchMock from "fetch-mock"; import { + FETCH_CHANGESETS, FETCH_CHANGESETS_FAILURE, FETCH_CHANGESETS_PENDING, FETCH_CHANGESETS_SUCCESS, fetchChangesetsByNamespaceAndName, - fetchChangesetsSuccess, getChangesetsForNameAndNamespaceFromState + fetchChangesetsSuccess, getChangesetsForNameAndNamespaceFromState, getFetchChangesetsFailure, isFetchChangesetsPending } from "./changesets"; import reducer from "./changesets"; @@ -26,10 +27,12 @@ describe("fetching of changesets", () => { fetchMock.getOnce(URL, "{}"); const expectedActions = [ - {type: FETCH_CHANGESETS_PENDING, payload: {namespace: "foo", name: "bar"}}, + {type: FETCH_CHANGESETS_PENDING, payload: {namespace: "foo", name: "bar"}, + itemId: "foo/bar"}, { type: FETCH_CHANGESETS_SUCCESS, - payload: {collection, namespace: "foo", name: "bar"} + payload: {collection, namespace: "foo", name: "bar"}, + itemId: "foo/bar" } ]; @@ -37,6 +40,27 @@ describe("fetching of changesets", () => { return store.dispatch(fetchChangesetsByNamespaceAndName("foo", "bar")).then(() => { expect(store.getActions()).toEqual(expectedActions); }); + }); + + it("should fail fetching changesets on error", () => { + fetchMock.getOnce(URL, 500); + + const expectedActions = [ + {type: FETCH_CHANGESETS_PENDING, payload: {namespace: "foo", name: "bar"}, + itemId: "foo/bar"}, + { + type: FETCH_CHANGESETS_SUCCESS, + payload: {collection, namespace: "foo", name: "bar"}, + itemId: "foo/bar" + } + ]; + + const store = mockStore({}); + return store.dispatch(fetchChangesetsByNamespaceAndName("foo", "bar")).then(() => { + expect(store.getActions()[0]).toEqual(expectedActions[0]); + expect(store.getActions()[1].type).toEqual(FETCH_CHANGESETS_FAILURE); + expect(store.getActions()[1].payload).toBeDefined(); + }); }) }); @@ -93,6 +117,8 @@ describe("changesets reducer", () => { }); describe("changeset selectors", () => { + const error = new Error("Something went wrong"); + it("should get all changesets for a given namespace and name", () => { const state = { changesets: { @@ -106,5 +132,34 @@ describe("changeset selectors", () => { }; const result = getChangesetsForNameAndNamespaceFromState("foo", "bar", state); expect(result).toContainEqual({id: "id1"}) + }); + + it("should return true, when fetching changesets is pending", () => { + const state = { + pending: { + [FETCH_CHANGESETS + "/foo/bar"]: true + } + }; + + expect(isFetchChangesetsPending(state, "foo", "bar")).toBeTruthy(); + }); + + it("should return false, when fetching changesets is not pending", () => { + expect(isFetchChangesetsPending({}, "foo", "bar")).toEqual(false); + }); + + it("should return error if fetching changesets failed", () => { + const state = { + failure: { + [FETCH_CHANGESETS + "/foo/bar"]: error + } + }; + + expect(getFetchChangesetsFailure(state, "foo", "bar")).toEqual(error); + }); + + it("should return false if fetching changesets did not fail", () => { + expect(getFetchChangesetsFailure({}, "foo", "bar")).toBeUndefined(); }) + }); From 5687a552b8fc91fb69a8149ee33b580d0df3214e Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Fri, 14 Sep 2018 16:15:13 +0200 Subject: [PATCH 05/81] Added possibility to fetch changesets by branches --- .../src/changesets/components/Changesets.js | 18 +- scm-ui/src/changesets/modules/changesets.js | 58 +++- .../src/changesets/modules/changesets.test.js | 296 +++++++++++------- scm-ui/src/createReduxStore.js | 2 + scm-ui/src/repos/modules/branches.js | 41 +++ scm-ui/src/repos/modules/branches.test.js | 92 +++++- 6 files changed, 359 insertions(+), 148 deletions(-) diff --git a/scm-ui/src/changesets/components/Changesets.js b/scm-ui/src/changesets/components/Changesets.js index ea0ac7aad1..f9feb51829 100644 --- a/scm-ui/src/changesets/components/Changesets.js +++ b/scm-ui/src/changesets/components/Changesets.js @@ -3,8 +3,12 @@ import { connect } from "react-redux"; import ChangesetRow from "./ChangesetRow"; import type {Changeset} from "@scm-manager/ui-types"; -import { fetchChangesetsByNamespaceAndName, getChangesetsForNameAndNamespaceFromState } from "../modules/changesets"; +import { + fetchChangesetsByNamespaceAndName, + getChangesets, +} from "../modules/changesets"; import { translate } from "react-i18next"; +import {fetchBranchesByNamespaceAndName} from "../../repos/modules/branches"; type Props = { changesets: Changeset[], @@ -15,6 +19,7 @@ class Changesets extends React.Component { componentDidMount() { const {namespace, name} = this.props.repository; this.props.fetchChangesetsByNamespaceAndName(namespace, name); + this.props.fetchBranchesByNamespaceAndName(namespace, name); } render() { @@ -24,7 +29,7 @@ class Changesets extends React.Component { } return
{t("author.name")}{t("changeset.description")}{t("changeset.date")}
- Changesets + {changesets.map((changeset, index) => { @@ -36,15 +41,20 @@ class Changesets extends React.Component { } const mapStateToProps = (state, ownProps) => { + const {namespace, name} = ownProps.repository; return { - changesets: getChangesetsForNameAndNamespaceFromState(ownProps.repository.namespace, ownProps.repository.name, state) + changesets: getChangesets(namespace, name, "", state) } }; const mapDispatchToProps = dispatch => { return { fetchChangesetsByNamespaceAndName: (namespace: string, name: string) => { - dispatch(fetchChangesetsByNamespaceAndName(namespace, name)) + dispatch(fetchChangesetsByNamespaceAndName(namespace, name)); + }, + + fetchBranchesByNamespaceAndName: (namespace: string, name: string) => { + dispatch(fetchBranchesByNamespaceAndName(namespace, name)); } } }; diff --git a/scm-ui/src/changesets/modules/changesets.js b/scm-ui/src/changesets/modules/changesets.js index 1aaa77f961..d77a1423aa 100644 --- a/scm-ui/src/changesets/modules/changesets.js +++ b/scm-ui/src/changesets/modules/changesets.js @@ -26,39 +26,61 @@ export function fetchChangesetsByNamespaceAndName(namespace: string, name: strin } } -export function fetchChangesetsPending(namespace: string, name: string): Action { +export function fetchChangesetsByNamespaceNameAndBranch(namespace: string, name: string, branch: string) { + return function (dispatch: any) { + dispatch(fetchChangesetsPending(namespace, name, branch)); + return apiClient.get(REPO_URL + "/" + namespace + "/" + name + "/branches/" + branch + "/changesets").then(response => response.json()) + .then(data => { + dispatch(fetchChangesetsSuccess(data, namespace, name, branch)) + }).catch(cause => { + dispatch(fetchChangesetsFailure(namespace, name, branch, cause)) + }) + } +} + +export function fetchChangesetsPending(namespace: string, name: string, branch?: string): Action { return { type: FETCH_CHANGESETS_PENDING, payload: { namespace, - name + name, + branch }, - itemId: namespace + "/" + name + itemId: createItemId(namespace, name, branch) } } -export function fetchChangesetsSuccess(collection: any, namespace: string, name: string): Action { +export function fetchChangesetsSuccess(collection: any, namespace: string, name: string, branch?: string): Action { return { type: FETCH_CHANGESETS_SUCCESS, - payload: {collection, namespace, name}, - itemId: namespace + "/" + name + payload: {collection, namespace, name, branch}, + itemId: createItemId(namespace, name, branch) } } -function fetchChangesetsFailure(namespace: string, name: string, error: Error): Action { +function fetchChangesetsFailure(namespace: string, name: string, branch?: string, error: Error): Action { return { type: FETCH_CHANGESETS_FAILURE, payload: { namespace, name, + branch, error }, - itemId: namespace + "/" + name + itemId: createItemId(namespace, name, branch) } } +function createItemId(namespace: string, name: string, branch?: string): string { + let itemId = namespace + "/" + name; + if (branch && branch !== "") { + itemId = itemId + "/" + branch; + } + return itemId; +} + // reducer -export default function reducer(state: any = {}, action: any = {}) { +export default function reducer(state: any = {}, action: Action = {type: "UNKNOWN"}): Object { switch (action.type) { case FETCH_CHANGESETS_SUCCESS: const {namespace, name} = action.payload; @@ -90,7 +112,7 @@ function extractChangesetsByIds(data: any, oldChangesetsByIds: any) { } //selectors -export function getChangesetsForNameAndNamespaceFromState(namespace: string, name: string, state: Object) { +export function getChangesetsForNamespaceAndNameFromState(namespace: string, name: string, state: Object) { const key = namespace + "/" + name; if (!state.changesets[key]) { return null; @@ -98,11 +120,19 @@ export function getChangesetsForNameAndNamespaceFromState(namespace: string, nam return Object.values(state.changesets[key].byId); } -export function isFetchChangesetsPending( state: Object, namespace: string, name: string) { - return isPending(state, FETCH_CHANGESETS, namespace + "/" + name) +export function getChangesets(namespace: string, name: string, branch: string, state: Object) { + const key = createItemId(namespace, name, branch); + if (!state.changesets[key]) { + return null; + } + return Object.values(state.changesets[key].byId); } -export function getFetchChangesetsFailure( state: Object, namespace: string, name: string) { - return getFailure(state, FETCH_CHANGESETS, namespace + "/" + name); +export function isFetchChangesetsPending(state: Object, namespace: string, name: string, branch?: string) { + return isPending(state, FETCH_CHANGESETS, createItemId(namespace, name, branch)) +} + +export function getFetchChangesetsFailure(state: Object, namespace: string, name: string, branch?: string) { + return getFailure(state, FETCH_CHANGESETS, createItemId(namespace, name, branch)); } diff --git a/scm-ui/src/changesets/modules/changesets.test.js b/scm-ui/src/changesets/modules/changesets.test.js index c514978144..e206178538 100644 --- a/scm-ui/src/changesets/modules/changesets.test.js +++ b/scm-ui/src/changesets/modules/changesets.test.js @@ -4,95 +4,129 @@ import configureMockStore from "redux-mock-store"; import thunk from "redux-thunk"; import fetchMock from "fetch-mock"; import { - FETCH_CHANGESETS, FETCH_CHANGESETS_FAILURE, + FETCH_CHANGESETS, + FETCH_CHANGESETS_FAILURE, FETCH_CHANGESETS_PENDING, FETCH_CHANGESETS_SUCCESS, fetchChangesetsByNamespaceAndName, - fetchChangesetsSuccess, getChangesetsForNameAndNamespaceFromState, getFetchChangesetsFailure, isFetchChangesetsPending + fetchChangesetsByNamespaceNameAndBranch, + fetchChangesetsSuccess, + getChangesets, + getChangesetsForNamespaceAndNameFromState, + getFetchChangesetsFailure, + isFetchChangesetsPending } from "./changesets"; import reducer from "./changesets"; const collection = {}; -describe("fetching of changesets", () => { - const URL = "/api/rest/v2/repositories/foo/bar/changesets"; - const mockStore = configureMockStore([thunk]); +describe("changesets", () => { + describe("fetching of changesets", () => { + const DEFAULT_BRANCH_URL = "/api/rest/v2/repositories/foo/bar/changesets"; + const SPECIFIC_BRANCH_URL = "/api/rest/v2/repositories/foo/bar/branches/specific/changesets"; + const mockStore = configureMockStore([thunk]); - afterEach(() => { - fetchMock.reset(); - fetchMock.restore(); - }); - - it("should fetch changesets", () => { - fetchMock.getOnce(URL, "{}"); - - const expectedActions = [ - {type: FETCH_CHANGESETS_PENDING, payload: {namespace: "foo", name: "bar"}, - itemId: "foo/bar"}, - { - type: FETCH_CHANGESETS_SUCCESS, - payload: {collection, namespace: "foo", name: "bar"}, - itemId: "foo/bar" - } - ]; - - const store = mockStore({}); - return store.dispatch(fetchChangesetsByNamespaceAndName("foo", "bar")).then(() => { - expect(store.getActions()).toEqual(expectedActions); + afterEach(() => { + fetchMock.reset(); + fetchMock.restore(); }); - }); - it("should fail fetching changesets on error", () => { - fetchMock.getOnce(URL, 500); + it("should fetch changesets for default branch", () => { + fetchMock.getOnce(DEFAULT_BRANCH_URL, "{}"); - const expectedActions = [ - {type: FETCH_CHANGESETS_PENDING, payload: {namespace: "foo", name: "bar"}, - itemId: "foo/bar"}, - { - type: FETCH_CHANGESETS_SUCCESS, - payload: {collection, namespace: "foo", name: "bar"}, - itemId: "foo/bar" - } - ]; + const expectedActions = [ + { + type: FETCH_CHANGESETS_PENDING, payload: {namespace: "foo", name: "bar"}, + itemId: "foo/bar" + }, + { + type: FETCH_CHANGESETS_SUCCESS, + payload: {collection, namespace: "foo", name: "bar"}, + itemId: "foo/bar" + } + ]; - const store = mockStore({}); - return store.dispatch(fetchChangesetsByNamespaceAndName("foo", "bar")).then(() => { - expect(store.getActions()[0]).toEqual(expectedActions[0]); - expect(store.getActions()[1].type).toEqual(FETCH_CHANGESETS_FAILURE); - expect(store.getActions()[1].payload).toBeDefined(); + const store = mockStore({}); + return store.dispatch(fetchChangesetsByNamespaceAndName("foo", "bar")).then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); }); - }) -}); -describe("changesets reducer", () => { - const responseBody = { - _embedded: { - changesets: [ - {id: "changeset1", author: {mail: "z@phod.com", name: "zaphod"}}, - {id: "changeset2", description: "foo"}, - {id: "changeset3", description: "bar"}, - ], - _embedded: { - tags: [], - branches: [], - parents: [] - } - } - }; + it("should fetch changesets for specific branch", () => { + fetchMock.getOnce(SPECIFIC_BRANCH_URL, "{}"); - it("should set state to received changesets", () => { - const newState = reducer({}, fetchChangesetsSuccess(responseBody, "foo", "bar")); - expect(newState).toBeDefined(); - expect(newState["foo/bar"].byId["changeset1"].author.mail).toEqual("z@phod.com"); - expect(newState["foo/bar"].byId["changeset2"].description).toEqual("foo"); - expect(newState["foo/bar"].byId["changeset3"].description).toEqual("bar"); + const expectedActions = [ + { + type: FETCH_CHANGESETS_PENDING, payload: {namespace: "foo", name: "bar", branch: "specific"}, + itemId: "foo/bar/specific" + }, + { + type: FETCH_CHANGESETS_SUCCESS, + payload: {collection, namespace: "foo", name: "bar", branch: "specific"}, + itemId: "foo/bar/specific" + } + ]; + + const store = mockStore({}); + return store.dispatch(fetchChangesetsByNamespaceNameAndBranch("foo", "bar", "specific")).then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); + }); + + it("should fail fetching changesets on error", () => { + fetchMock.getOnce(DEFAULT_BRANCH_URL, 500); + + const expectedActions = [ + { + type: FETCH_CHANGESETS_PENDING, payload: {namespace: "foo", name: "bar"}, + itemId: "foo/bar" + }, + { + type: FETCH_CHANGESETS_SUCCESS, + payload: {collection, namespace: "foo", name: "bar"}, + itemId: "foo/bar" + } + ]; + + const store = mockStore({}); + return store.dispatch(fetchChangesetsByNamespaceAndName("foo", "bar")).then(() => { + expect(store.getActions()[0]).toEqual(expectedActions[0]); + expect(store.getActions()[1].type).toEqual(FETCH_CHANGESETS_FAILURE); + expect(store.getActions()[1].payload).toBeDefined(); + }); + }) + + it("should fail fetching changesets for specific branch on error", () => { + fetchMock.getOnce(SPECIFIC_BRANCH_URL, 500); + + const expectedActions = [ + { + type: FETCH_CHANGESETS_PENDING, payload: {namespace: "foo", name: "bar", branch: "specific"}, + itemId: "foo/bar/specific" + }, + { + type: FETCH_CHANGESETS_SUCCESS, + payload: {collection, namespace: "foo", name: "bar", branch: "specific"}, + itemId: "foo/bar/specific" + } + ]; + + const store = mockStore({}); + return store.dispatch(fetchChangesetsByNamespaceNameAndBranch("foo", "bar", "specific")).then(() => { + expect(store.getActions()[0]).toEqual(expectedActions[0]); + expect(store.getActions()[1].type).toEqual(FETCH_CHANGESETS_FAILURE); + expect(store.getActions()[1].payload).toBeDefined(); + }); + }) }); - it("should not delete existing changesets from state", () => { + describe("changesets reducer", () => { const responseBody = { _embedded: { changesets: [ {id: "changeset1", author: {mail: "z@phod.com", name: "zaphod"}}, + {id: "changeset2", description: "foo"}, + {id: "changeset3", description: "bar"}, ], _embedded: { tags: [], @@ -101,65 +135,89 @@ describe("changesets reducer", () => { } } }; - const newState = reducer({ - "foo/bar": { - byId: { - ["changeset2"]: { - id: "changeset2", - author: {mail: "mail@author.com", name: "author"} + + it("should set state to received changesets", () => { + const newState = reducer({}, fetchChangesetsSuccess(responseBody, "foo", "bar")); + expect(newState).toBeDefined(); + expect(newState["foo/bar"].byId["changeset1"].author.mail).toEqual("z@phod.com"); + expect(newState["foo/bar"].byId["changeset2"].description).toEqual("foo"); + expect(newState["foo/bar"].byId["changeset3"].description).toEqual("bar"); + }); + + 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: [] } } - } - }, fetchChangesetsSuccess(responseBody, "foo", "bar")); - expect(newState["foo/bar"].byId["changeset2"]).toBeDefined(); - expect(newState["foo/bar"].byId["changeset1"]).toBeDefined(); - }) -}); - -describe("changeset selectors", () => { - const error = new Error("Something went wrong"); - - it("should get all changesets for a given namespace and name", () => { - const state = { - changesets: { - ["foo/bar"]: { + }; + const newState = reducer({ + "foo/bar": { byId: { - "id1": {id: "id1"}, - "id2": {id: "id2"} + ["changeset2"]: { + id: "changeset2", + author: {mail: "mail@author.com", name: "author"} + } } } - } - }; - const result = getChangesetsForNameAndNamespaceFromState("foo", "bar", state); - expect(result).toContainEqual({id: "id1"}) + }, fetchChangesetsSuccess(responseBody, "foo", "bar")); + expect(newState["foo/bar"].byId["changeset2"]).toBeDefined(); + expect(newState["foo/bar"].byId["changeset1"]).toBeDefined(); + }) }); - it("should return true, when fetching changesets is pending", () => { - const state = { - pending: { - [FETCH_CHANGESETS + "/foo/bar"]: true - } - }; + describe("changeset selectors", () => { + const error = new Error("Something went wrong"); + + it("should get all changesets for a given namespace and name", () => { + const state = { + changesets: { + ["foo/bar"]: { + byId: { + "id1": {id: "id1"}, + "id2": {id: "id2"} + } + } + } + }; + // const result = getChangesetsForNamespaceAndNameFromState("foo", "bar", state); + const result = getChangesets("foo", "bar", "", state); + expect(result).toContainEqual({id: "id1"}) + }); + + it("should return true, when fetching changesets is pending", () => { + const state = { + pending: { + [FETCH_CHANGESETS + "/foo/bar"]: true + } + }; + + expect(isFetchChangesetsPending(state, "foo", "bar")).toBeTruthy(); + }); + + it("should return false, when fetching changesets is not pending", () => { + expect(isFetchChangesetsPending({}, "foo", "bar")).toEqual(false); + }); + + it("should return error if fetching changesets failed", () => { + const state = { + failure: { + [FETCH_CHANGESETS + "/foo/bar"]: error + } + }; + + expect(getFetchChangesetsFailure(state, "foo", "bar")).toEqual(error); + }); + + it("should return false if fetching changesets did not fail", () => { + expect(getFetchChangesetsFailure({}, "foo", "bar")).toBeUndefined(); + }) - expect(isFetchChangesetsPending(state, "foo", "bar")).toBeTruthy(); }); - - it("should return false, when fetching changesets is not pending", () => { - expect(isFetchChangesetsPending({}, "foo", "bar")).toEqual(false); - }); - - it("should return error if fetching changesets failed", () => { - const state = { - failure: { - [FETCH_CHANGESETS + "/foo/bar"]: error - } - }; - - expect(getFetchChangesetsFailure(state, "foo", "bar")).toEqual(error); - }); - - it("should return false if fetching changesets did not fail", () => { - expect(getFetchChangesetsFailure({}, "foo", "bar")).toBeUndefined(); - }) - }); diff --git a/scm-ui/src/createReduxStore.js b/scm-ui/src/createReduxStore.js index a63a6edcdb..5c4fb20410 100644 --- a/scm-ui/src/createReduxStore.js +++ b/scm-ui/src/createReduxStore.js @@ -15,6 +15,7 @@ import failure from "./modules/failure"; import config from "./config/modules/config"; import type { BrowserHistory } from "history/createBrowserHistory"; +import branches from "./repos/modules/branches"; function createReduxStore(history: BrowserHistory) { const composeEnhancers = @@ -28,6 +29,7 @@ function createReduxStore(history: BrowserHistory) { repos, repositoryTypes, changesets, + branches, groups, auth, config diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index dde5cb379b..4f1624184f 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -50,4 +50,45 @@ export function fetchBranchesFailure(namespace: string, name: string, error: Err // Reducers +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]: {}}; + if (state[key] !== undefined) { + oldBranchesByNames[key] = state[key] + } + return { + [key]: { + byNames: extractBranchesByNames(action.payload.data, oldBranchesByNames[key].byNames) + } + }; + default: + return state; + } + +} + +function extractBranchesByNames(data: any, oldBranchesByNames: any): Branch[] { + const branches = data._embedded.branches; + const branchesByNames = {}; + + for (let branch of branches) { + branchesByNames[branch.name] = branch; + } + + for (let name in oldBranchesByNames) { + branchesByNames[name] = oldBranchesByNames[name] + } + return branchesByNames; +} + // 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); +} diff --git a/scm-ui/src/repos/modules/branches.test.js b/scm-ui/src/repos/modules/branches.test.js index 67c5950c5a..83a6f73b03 100644 --- a/scm-ui/src/repos/modules/branches.test.js +++ b/scm-ui/src/repos/modules/branches.test.js @@ -5,13 +5,25 @@ import { FETCH_BRANCHES_FAILURE, FETCH_BRANCHES_PENDING, FETCH_BRANCHES_SUCCESS, - fetchBranchesByNamespaceAndName + fetchBranchesByNamespaceAndName, getBranchesForNamespaceAndNameFromState } from "./branches"; +import reducer from "./branches"; + + +const namespace = "foo"; +const name = "bar"; +const key = namespace + "/" + name; + +const branch1 = {name: "branch1", revision: "revision1"}; +const branch2 = {name: "branch2", revision: "revision2"}; +const branch3 = {name: "branch3", revision: "revision3"}; + describe("fetch branches", () => { const URL = "/api/rest/v2/repositories/foo/bar/branches"; const mockStore = configureMockStore([thunk]); + afterEach(() => { fetchMock.reset(); fetchMock.restore(); @@ -24,17 +36,19 @@ describe("fetch branches", () => { fetchMock.getOnce(URL, "{}"); const expectedActions = [ - {type: FETCH_BRANCHES_PENDING, payload: {namespace: "foo", name: "bar"}, - itemId: "foo/bar"}, + { + type: FETCH_BRANCHES_PENDING, payload: {namespace, name}, + itemId: key + }, { type: FETCH_BRANCHES_SUCCESS, - payload: {data: collection, namespace: "foo", name: "bar"}, - itemId: "foo/bar" + payload: {data: collection, namespace, name}, + itemId: key } ]; const store = mockStore({}); - return store.dispatch(fetchBranchesByNamespaceAndName("foo", "bar")).then(() => { + return store.dispatch(fetchBranchesByNamespaceAndName(namespace, name)).then(() => { expect(store.getActions()).toEqual(expectedActions); }); }); @@ -45,19 +59,75 @@ describe("fetch branches", () => { fetchMock.getOnce(URL, 500); const expectedActions = [ - {type: FETCH_BRANCHES_PENDING, payload: {namespace: "foo", name: "bar"}, - itemId: "foo/bar"}, + { + type: FETCH_BRANCHES_PENDING, payload: {namespace, name}, + itemId: key + }, { type: FETCH_BRANCHES_FAILURE, - payload: {error: collection, namespace: "foo", name: "bar"}, - itemId: "foo/bar" + payload: {error: collection, namespace, name}, + itemId: key } ]; const store = mockStore({}); - return store.dispatch(fetchBranchesByNamespaceAndName("foo", "bar")).then(() => { + return store.dispatch(fetchBranchesByNamespaceAndName(namespace, name)).then(() => { expect(store.getActions()[0]).toEqual(expectedActions[0]); expect(store.getActions()[1].type).toEqual(FETCH_BRANCHES_FAILURE); }); }) }); + +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]: { + byNames: { + "branch1": branch1 + } + } + } + }; + getBranchesForNamespaceAndNameFromState(namespace, name, state); + }) +}); From 2931877c9ff1889861c3dafc0ea2753a892dc42e Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Mon, 17 Sep 2018 14:01:51 +0200 Subject: [PATCH 06/81] Added DropDown.js --- scm-ui/src/changesets/components/DropDown.js | 25 ++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 scm-ui/src/changesets/components/DropDown.js diff --git a/scm-ui/src/changesets/components/DropDown.js b/scm-ui/src/changesets/components/DropDown.js new file mode 100644 index 0000000000..28d3ef28f0 --- /dev/null +++ b/scm-ui/src/changesets/components/DropDown.js @@ -0,0 +1,25 @@ +// @flow + +import React from "react"; + +type Props = { + options: string[], + optionSelected: string => void +} + +class DropDown extends React.Component { + render() { + const {options} = this.props; + return + } + + change = (event) => { + this.props.optionSelected(event.target.value); + } +} + +export default DropDown; From 4cb644f9f8aee2f23e04affb665979b210ef52e8 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Mon, 17 Sep 2018 14:03:13 +0200 Subject: [PATCH 07/81] Restructured Changeset Component WIP --- .../changesets/components/ChangesetTable.js | 28 ++++++ .../src/changesets/components/Changesets.js | 65 -------------- .../src/changesets/containers/Changesets.js | 85 +++++++++++++++++++ scm-ui/src/changesets/modules/changesets.js | 8 +- .../src/repos/components/RepositoryDetails.js | 2 - scm-ui/src/repos/containers/RepositoryRoot.js | 35 ++++---- scm-ui/src/repos/modules/branches.js | 8 ++ scm-ui/src/repos/modules/branches.test.js | 25 +++++- 8 files changed, 167 insertions(+), 89 deletions(-) create mode 100644 scm-ui/src/changesets/components/ChangesetTable.js delete mode 100644 scm-ui/src/changesets/components/Changesets.js create mode 100644 scm-ui/src/changesets/containers/Changesets.js diff --git a/scm-ui/src/changesets/components/ChangesetTable.js b/scm-ui/src/changesets/components/ChangesetTable.js new file mode 100644 index 0000000000..29260bdc32 --- /dev/null +++ b/scm-ui/src/changesets/components/ChangesetTable.js @@ -0,0 +1,28 @@ +// @flow +import ChangesetRow from "./ChangesetRow"; +import React from "react"; + +type Props = { + changesets: Changeset[] +} + +class ChangesetTable extends React.Component { + + render() { + const {changesets} = this.props; + return
Changesets
+ + + + + + + {changesets.map((changeset, index) => { + return ; + })} + +
Changesets
+ } +} + +export default ChangesetTable; diff --git a/scm-ui/src/changesets/components/Changesets.js b/scm-ui/src/changesets/components/Changesets.js deleted file mode 100644 index f9feb51829..0000000000 --- a/scm-ui/src/changesets/components/Changesets.js +++ /dev/null @@ -1,65 +0,0 @@ -import React from "react" -import { connect } from "react-redux"; -import ChangesetRow from "./ChangesetRow"; -import type {Changeset} from "@scm-manager/ui-types"; - -import { - fetchChangesetsByNamespaceAndName, - getChangesets, -} from "../modules/changesets"; -import { translate } from "react-i18next"; -import {fetchBranchesByNamespaceAndName} from "../../repos/modules/branches"; - -type Props = { - changesets: Changeset[], - t: string => string -} - -class Changesets extends React.Component { - componentDidMount() { - const {namespace, name} = this.props.repository; - this.props.fetchChangesetsByNamespaceAndName(namespace, name); - this.props.fetchBranchesByNamespaceAndName(namespace, name); - } - - render() { - const { t, changesets } = this.props; - if (!changesets) { - return null; - } - return - - - - - {changesets.map((changeset, index) => { - return ; - })} - -
Changesets
- } -} - -const mapStateToProps = (state, ownProps) => { - const {namespace, name} = ownProps.repository; - return { - changesets: getChangesets(namespace, name, "", state) - } -}; - -const mapDispatchToProps = dispatch => { - return { - fetchChangesetsByNamespaceAndName: (namespace: string, name: string) => { - dispatch(fetchChangesetsByNamespaceAndName(namespace, name)); - }, - - fetchBranchesByNamespaceAndName: (namespace: string, name: string) => { - dispatch(fetchBranchesByNamespaceAndName(namespace, name)); - } - } -}; - -export default connect( - mapStateToProps, - mapDispatchToProps -)(translate("changesets")(Changesets)); diff --git a/scm-ui/src/changesets/containers/Changesets.js b/scm-ui/src/changesets/containers/Changesets.js new file mode 100644 index 0000000000..2cc64f8bb5 --- /dev/null +++ b/scm-ui/src/changesets/containers/Changesets.js @@ -0,0 +1,85 @@ +import React from "react" +import {connect} from "react-redux"; + +import { + fetchChangesetsByNamespaceAndName, fetchChangesetsByNamespaceNameAndBranch, + getChangesets, +} from "../modules/changesets"; +import {translate} from "react-i18next"; +import {fetchBranchesByNamespaceAndName, getBranchNames} from "../../repos/modules/branches"; +import type {Repository} from "@scm-manager/ui-types"; +import ChangesetTable from "../components/ChangesetTable"; +import DropDown from "../components/DropDown"; + +type Props = { + repository: Repository, + branchName: string, + history: History, + fetchChangesetsByNamespaceNameAndBranch: (namespace: string, name: string, branch: string) => void +} + +type State = { + branchName: string +} + +class Changesets extends React.Component { + constructor(props) { + super(props); + this.state = {}; + } + + componentDidMount() { + const {namespace, name} = this.props.repository; + this.props.fetchChangesetsByNamespaceNameAndBranch(namespace, name, this.props.branchName); + this.props.fetchBranchesByNamespaceAndName(namespace, name); + } + + render() { + const {changesets, branchNames} = this.props; + if (changesets === null) { + return null + } + if (branchNames) { + return
+ + +
; + } else { + return + } + + } + + branchChanged = (branchName: string) => { + this.props.history.push("./history/" + branchName) + }; +} + + +const mapStateToProps = (state, ownProps: Props) => { + console.log("mapStateToProps ownProps: ", ownProps); + const {namespace, name} = ownProps.repository; + return { + changesets: getChangesets(namespace, name, ownProps.branchName, state), + branchNames: getBranchNames(namespace, name, state) + } +}; + +const mapDispatchToProps = dispatch => { + return { + fetchChangesetsByNamespaceAndName: (namespace: string, name: string) => { + dispatch(fetchChangesetsByNamespaceAndName(namespace, name)); + }, + fetchChangesetsByNamespaceNameAndBranch: (namespace: string, name: string, branch: string) => { + dispatch(fetchChangesetsByNamespaceNameAndBranch(namespace, name, branch)); + }, + fetchBranchesByNamespaceAndName: (namespace: string, name: string) => { + dispatch(fetchBranchesByNamespaceAndName(namespace, name)); + } + } +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(Changesets); diff --git a/scm-ui/src/changesets/modules/changesets.js b/scm-ui/src/changesets/modules/changesets.js index d77a1423aa..cd35801a2f 100644 --- a/scm-ui/src/changesets/modules/changesets.js +++ b/scm-ui/src/changesets/modules/changesets.js @@ -83,14 +83,13 @@ function createItemId(namespace: string, name: string, branch?: string): string export default function reducer(state: any = {}, action: Action = {type: "UNKNOWN"}): Object { switch (action.type) { case FETCH_CHANGESETS_SUCCESS: - const {namespace, name} = action.payload; - const key = namespace + "/" + name; - + const {namespace, name, branch} = action.payload; + const key = createItemId(namespace, name, branch); let oldChangesets = {[key]: {}}; if (state[key] !== undefined) { oldChangesets[key] = state[key] } - return {[key]: {byId: extractChangesetsByIds(action.payload.collection, oldChangesets[key].byId)}}; + return {...state, [key]: {byId: extractChangesetsByIds(action.payload.collection, oldChangesets[key].byId)}}; default: return state; } @@ -122,6 +121,7 @@ export function getChangesetsForNamespaceAndNameFromState(namespace: string, nam export function getChangesets(namespace: string, name: string, branch: string, state: Object) { const key = createItemId(namespace, name, branch); + console.log("getChangesets for key " + key); if (!state.changesets[key]) { return null; } diff --git a/scm-ui/src/repos/components/RepositoryDetails.js b/scm-ui/src/repos/components/RepositoryDetails.js index 33edaff541..99c88fec94 100644 --- a/scm-ui/src/repos/components/RepositoryDetails.js +++ b/scm-ui/src/repos/components/RepositoryDetails.js @@ -3,7 +3,6 @@ import React from "react"; import type { Repository } from "@scm-manager/ui-types"; import RepositoryDetailTable from "./RepositoryDetailTable"; import { ExtensionPoint } from "@scm-manager/ui-extensions"; -import Changesets from "../../changesets/components/Changesets"; type Props = { repository: Repository @@ -21,7 +20,6 @@ class RepositoryDetails extends React.Component { renderAll={true} props={{ repository }} /> - ); diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 2816cdb5c5..0f6f6ada20 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,13 +18,14 @@ 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/containers/Changesets"; type Props = { namespace: string, @@ -45,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); } @@ -70,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 ( @@ -83,7 +84,7 @@ class RepositoryRoot extends React.Component { } if (!repository || loading) { - return ; + return ; } const url = this.matchedUrl(); @@ -95,22 +96,26 @@ class RepositoryRoot extends React.Component { } + component={() => } /> } + component={() => } + /> + } />
- - + +
- - + +
@@ -121,7 +126,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/branches.js b/scm-ui/src/repos/modules/branches.js index 4f1624184f..fb1b222aa9 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -92,3 +92,11 @@ export function getBranchesForNamespaceAndNameFromState(namespace: string, name: } return Object.values(state.branches[key].byNames); } + +export function getBranchNames(namespace: string, name: string, state: Object) { + const key = namespace + "/" + name; + if (!state.branches[key]) { + return null; + } + return Object.keys(state.branches[key].byNames); +} diff --git a/scm-ui/src/repos/modules/branches.test.js b/scm-ui/src/repos/modules/branches.test.js index 83a6f73b03..d97d9aa7bf 100644 --- a/scm-ui/src/repos/modules/branches.test.js +++ b/scm-ui/src/repos/modules/branches.test.js @@ -5,7 +5,7 @@ import { FETCH_BRANCHES_FAILURE, FETCH_BRANCHES_PENDING, FETCH_BRANCHES_SUCCESS, - fetchBranchesByNamespaceAndName, getBranchesForNamespaceAndNameFromState + fetchBranchesByNamespaceAndName, getBranchesForNamespaceAndNameFromState, getBranchNames } from "./branches"; import reducer from "./branches"; @@ -128,6 +128,25 @@ describe("branch selectors", () => { } } }; - getBranchesForNamespaceAndNameFromState(namespace, name, state); - }) + 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(namespace, name, state); + expect(names.length).toEqual(2); + expect(names).toContain("branch1"); + expect(names).toContain("branch2"); + }); }); From 2be854e3d2bdfb4cff2a68d8f85a6fe50b499c65 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Mon, 17 Sep 2018 14:16:43 +0200 Subject: [PATCH 08/81] Don't push /history-prefix to path WIP --- scm-ui/src/changesets/containers/Changesets.js | 2 +- scm-ui/src/repos/containers/RepositoryRoot.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/scm-ui/src/changesets/containers/Changesets.js b/scm-ui/src/changesets/containers/Changesets.js index 2cc64f8bb5..a197ae293d 100644 --- a/scm-ui/src/changesets/containers/Changesets.js +++ b/scm-ui/src/changesets/containers/Changesets.js @@ -51,7 +51,7 @@ class Changesets extends React.Component { } branchChanged = (branchName: string) => { - this.props.history.push("./history/" + branchName) + this.props.history.push(branchName) }; } diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 0f6f6ada20..7c8bae308f 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -106,6 +106,10 @@ class RepositoryRoot extends React.Component { path={`${url}/history`} component={() => } /> + {/* }*/} + {/*/>*/}
From c5da25a1b435122e9b6363e39cac1a228bb5f96d Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Mon, 17 Sep 2018 16:31:19 +0200 Subject: [PATCH 09/81] Determine branch for which to show changesets via URL --- scm-ui/src/changesets/components/DropDown.js | 10 ++++--- .../src/changesets/containers/Changesets.js | 29 +++++++++++-------- scm-ui/src/repos/containers/RepositoryRoot.js | 8 ++--- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/scm-ui/src/changesets/components/DropDown.js b/scm-ui/src/changesets/components/DropDown.js index 28d3ef28f0..8d73ea4fa5 100644 --- a/scm-ui/src/changesets/components/DropDown.js +++ b/scm-ui/src/changesets/components/DropDown.js @@ -4,15 +4,17 @@ import React from "react"; type Props = { options: string[], - optionSelected: string => void + optionSelected: string => void, + preselectedOption: string } class DropDown extends React.Component { render() { - const {options} = this.props; - return {options.map(option => { - return + return })} } diff --git a/scm-ui/src/changesets/containers/Changesets.js b/scm-ui/src/changesets/containers/Changesets.js index a197ae293d..48e95dd5d6 100644 --- a/scm-ui/src/changesets/containers/Changesets.js +++ b/scm-ui/src/changesets/containers/Changesets.js @@ -5,11 +5,12 @@ import { fetchChangesetsByNamespaceAndName, fetchChangesetsByNamespaceNameAndBranch, getChangesets, } from "../modules/changesets"; -import {translate} from "react-i18next"; +import type { History } from "history"; import {fetchBranchesByNamespaceAndName, getBranchNames} from "../../repos/modules/branches"; import type {Repository} from "@scm-manager/ui-types"; import ChangesetTable from "../components/ChangesetTable"; import DropDown from "../components/DropDown"; +import {withRouter} from "react-router-dom"; type Props = { repository: Repository, @@ -18,30 +19,34 @@ type Props = { fetchChangesetsByNamespaceNameAndBranch: (namespace: string, name: string, branch: string) => void } -type State = { - branchName: string -} class Changesets extends React.Component { constructor(props) { super(props); - this.state = {}; + this.state = { + }; } componentDidMount() { const {namespace, name} = this.props.repository; - this.props.fetchChangesetsByNamespaceNameAndBranch(namespace, name, this.props.branchName); + const branchName = this.props.match.params.branch; + if (branchName) { + this.props.fetchChangesetsByNamespaceNameAndBranch(namespace, name, branchName); + } else { + this.props.fetchChangesetsByNamespaceAndName(namespace, name); + } this.props.fetchBranchesByNamespaceAndName(namespace, name); } render() { const {changesets, branchNames} = this.props; + const branch = this.props.match.params.branch; if (changesets === null) { return null } if (branchNames) { return
- + this.branchChanged(branch)}/>
; } else { @@ -51,16 +56,16 @@ class Changesets extends React.Component { } branchChanged = (branchName: string) => { - this.props.history.push(branchName) + const { history, repository } = this.props; + history.push(`/repo/${repository.namespace}/${repository.name}/history/${branchName}`); }; } const mapStateToProps = (state, ownProps: Props) => { - console.log("mapStateToProps ownProps: ", ownProps); const {namespace, name} = ownProps.repository; return { - changesets: getChangesets(namespace, name, ownProps.branchName, state), + changesets: getChangesets(namespace, name, ownProps.match.params.branch, state), branchNames: getBranchNames(namespace, name, state) } }; @@ -79,7 +84,7 @@ const mapDispatchToProps = dispatch => { } }; -export default connect( +export default withRouter(connect( mapStateToProps, mapDispatchToProps -)(Changesets); +)(Changesets)); diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 7c8bae308f..75a938279c 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -103,13 +103,9 @@ class RepositoryRoot extends React.Component { component={() => } /> } + path={`${url}/history/:branch`} + component={() => } /> - {/* }*/} - {/*/>*/}
From 2af345bba0c767d49c62aad2af745332b954f237 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Mon, 17 Sep 2018 16:51:09 +0200 Subject: [PATCH 10/81] Added navigational link to repo's history --- scm-ui/public/locales/en/repos.json | 3 ++- scm-ui/src/changesets/components/DropDown.js | 1 + scm-ui/src/changesets/modules/changesets.js | 3 +-- scm-ui/src/repos/containers/RepositoryRoot.js | 7 +++++++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 7db2623247..678a7a07b4 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -22,7 +22,8 @@ "actions-label": "Actions", "back-label": "Back", "navigation-label": "Navigation", - "information": "Information" + "information": "Information", + "history": "History" }, "create": { "title": "Create Repository", diff --git a/scm-ui/src/changesets/components/DropDown.js b/scm-ui/src/changesets/components/DropDown.js index 8d73ea4fa5..c045df4af4 100644 --- a/scm-ui/src/changesets/components/DropDown.js +++ b/scm-ui/src/changesets/components/DropDown.js @@ -12,6 +12,7 @@ class DropDown extends React.Component { render() { const {options, preselectedOption} = this.props; return - + return
+ +
} change = (event) => { From 732a84bb7cf2d068dc64783063ac56fd5cb10909 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Tue, 18 Sep 2018 15:07:30 +0200 Subject: [PATCH 17/81] Fixed hiding of branch drop down when there are no branches --- .../changesets/components/ChangesetTable.js | 26 ++++++++++--------- .../src/changesets/containers/Changesets.js | 13 +++++----- scm-ui/src/repos/modules/branches.js | 2 +- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/scm-ui/src/changesets/components/ChangesetTable.js b/scm-ui/src/changesets/components/ChangesetTable.js index 29260bdc32..0658e213e3 100644 --- a/scm-ui/src/changesets/components/ChangesetTable.js +++ b/scm-ui/src/changesets/components/ChangesetTable.js @@ -10,18 +10,20 @@ class ChangesetTable extends React.Component { render() { const {changesets} = this.props; - return - - - - - - - {changesets.map((changeset, index) => { - return ; - })} - -
Changesets
+ return
+ + + + + + + + {changesets.map((changeset, index) => { + return ; + })} + +
Changesets
+
} } diff --git a/scm-ui/src/changesets/containers/Changesets.js b/scm-ui/src/changesets/containers/Changesets.js index 64b56fa559..592cc6d9e5 100644 --- a/scm-ui/src/changesets/containers/Changesets.js +++ b/scm-ui/src/changesets/containers/Changesets.js @@ -30,18 +30,19 @@ class Changesets extends React.Component { componentDidMount() { const {namespace, name} = this.props.repository; const branchName = this.props.match.params.branch; + const {fetchChangesetsByNamespaceNameAndBranch, fetchChangesetsByNamespaceAndName, fetchBranchesByNamespaceAndName} = this.props; if (branchName) { - this.props.fetchChangesetsByNamespaceNameAndBranch(namespace, name, branchName); + fetchChangesetsByNamespaceNameAndBranch(namespace, name, branchName); } else { - this.props.fetchChangesetsByNamespaceAndName(namespace, name); + fetchChangesetsByNamespaceAndName(namespace, name); } - this.props.fetchBranchesByNamespaceAndName(namespace, name); + fetchBranchesByNamespaceAndName(namespace, name); } render() { const {changesets, loading, error} = this.props; if (loading || !changesets) { - return + return } return
@@ -54,9 +55,9 @@ class Changesets extends React.Component { const branch = this.props.match.params.branch; const {changesets, branchNames} = this.props; - if (branchNames) { + if (branchNames && branchNames.length > 0) { return
- Branch: this.branchChanged(branch)}/>
; diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index fb1b222aa9..82c8940df4 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -95,7 +95,7 @@ export function getBranchesForNamespaceAndNameFromState(namespace: string, name: export function getBranchNames(namespace: string, name: string, state: Object) { const key = namespace + "/" + name; - if (!state.branches[key]) { + if (!state.branches[key] || !state.branches[key].byNames) { return null; } return Object.keys(state.branches[key].byNames); From a56dab4d8a521f3ac84b40d0f8dd261fbc78580e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 18 Sep 2018 15:19:01 +0200 Subject: [PATCH 18/81] Fix edit link for repositories in list view --- scm-ui/src/repos/components/list/RepositoryEntry.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scm-ui/src/repos/components/list/RepositoryEntry.js b/scm-ui/src/repos/components/list/RepositoryEntry.js index c28bc6b1dd..bc170144aa 100644 --- a/scm-ui/src/repos/components/list/RepositoryEntry.js +++ b/scm-ui/src/repos/components/list/RepositoryEntry.js @@ -67,10 +67,7 @@ class RepositoryEntry extends React.Component { renderModifyLink = (repository: Repository, repositoryLink: string) => { if (repository._links["update"]) { return ( - + ); } return null; From f75ede4bf03f4010025d5fe24e07857cdeb0b229 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Tue, 18 Sep 2018 16:30:37 +0200 Subject: [PATCH 19/81] Restructured changeset module --- scm-ui/src/changesets/modules/changesets.js | 18 ++++------ .../src/changesets/modules/changesets.test.js | 35 ++++++++----------- 2 files changed, 21 insertions(+), 32 deletions(-) diff --git a/scm-ui/src/changesets/modules/changesets.js b/scm-ui/src/changesets/modules/changesets.js index 161f03d48f..6423dca515 100644 --- a/scm-ui/src/changesets/modules/changesets.js +++ b/scm-ui/src/changesets/modules/changesets.js @@ -39,21 +39,18 @@ export function fetchChangesetsByNamespaceNameAndBranch(namespace: string, name: } export function fetchChangesetsPending(namespace: string, name: string, branch?: string): Action { + const itemId = createItemId(namespace, name, branch); return { type: FETCH_CHANGESETS_PENDING, - payload: { - namespace, - name, - branch - }, - itemId: createItemId(namespace, name, branch) + payload: itemId, + itemId } } -export function fetchChangesetsSuccess(collection: any, namespace: string, name: string, branch?: string): Action { +export function fetchChangesetsSuccess(changesets: any, namespace: string, name: string, branch?: string): Action { return { type: FETCH_CHANGESETS_SUCCESS, - payload: {collection, namespace, name, branch}, + payload: changesets, itemId: createItemId(namespace, name, branch) } } @@ -83,13 +80,12 @@ function createItemId(namespace: string, name: string, branch?: string): string export default function reducer(state: any = {}, action: Action = {type: "UNKNOWN"}): Object { switch (action.type) { case FETCH_CHANGESETS_SUCCESS: - const {namespace, name, branch} = action.payload; - const key = createItemId(namespace, name, branch); + const key = action.itemId let oldChangesets = {[key]: {}}; if (state[key] !== undefined) { oldChangesets[key] = state[key] } - return {...state, [key]: {byId: extractChangesetsByIds(action.payload.collection, oldChangesets[key].byId)}}; + return {...state, [key]: {byId: extractChangesetsByIds(action.payload, oldChangesets[key].byId)}}; default: return state; } diff --git a/scm-ui/src/changesets/modules/changesets.test.js b/scm-ui/src/changesets/modules/changesets.test.js index fad8d8a6a8..c74eee3975 100644 --- a/scm-ui/src/changesets/modules/changesets.test.js +++ b/scm-ui/src/changesets/modules/changesets.test.js @@ -17,7 +17,7 @@ import { } from "./changesets"; import reducer from "./changesets"; -const collection = {}; +const changesets = {}; describe("changesets", () => { describe("fetching of changesets", () => { @@ -35,12 +35,12 @@ describe("changesets", () => { const expectedActions = [ { - type: FETCH_CHANGESETS_PENDING, payload: {namespace: "foo", name: "bar"}, + type: FETCH_CHANGESETS_PENDING, payload: "foo/bar", itemId: "foo/bar" }, { type: FETCH_CHANGESETS_SUCCESS, - payload: {collection, namespace: "foo", name: "bar"}, + payload: changesets, itemId: "foo/bar" } ]; @@ -52,17 +52,18 @@ describe("changesets", () => { }); it("should fetch changesets for specific branch", () => { + const itemId = "foo/bar/specific"; fetchMock.getOnce(SPECIFIC_BRANCH_URL, "{}"); const expectedActions = [ { - type: FETCH_CHANGESETS_PENDING, payload: {namespace: "foo", name: "bar", branch: "specific"}, - itemId: "foo/bar/specific" + type: FETCH_CHANGESETS_PENDING, payload: itemId, + itemId }, { type: FETCH_CHANGESETS_SUCCESS, - payload: {collection, namespace: "foo", name: "bar", branch: "specific"}, - itemId: "foo/bar/specific" + payload: changesets, + itemId } ]; @@ -73,17 +74,13 @@ describe("changesets", () => { }); it("should fail fetching changesets on error", () => { + const itemId = "foo/bar"; fetchMock.getOnce(DEFAULT_BRANCH_URL, 500); const expectedActions = [ { - type: FETCH_CHANGESETS_PENDING, payload: {namespace: "foo", name: "bar"}, - itemId: "foo/bar" - }, - { - type: FETCH_CHANGESETS_SUCCESS, - payload: {collection, namespace: "foo", name: "bar"}, - itemId: "foo/bar" + type: FETCH_CHANGESETS_PENDING, payload: itemId, + itemId } ]; @@ -96,17 +93,13 @@ describe("changesets", () => { }) it("should fail fetching changesets for specific branch on error", () => { + const itemId = "foo/bar/specific"; fetchMock.getOnce(SPECIFIC_BRANCH_URL, 500); const expectedActions = [ { - type: FETCH_CHANGESETS_PENDING, payload: {namespace: "foo", name: "bar", branch: "specific"}, - itemId: "foo/bar/specific" - }, - { - type: FETCH_CHANGESETS_SUCCESS, - payload: {collection, namespace: "foo", name: "bar", branch: "specific"}, - itemId: "foo/bar/specific" + type: FETCH_CHANGESETS_PENDING, payload: itemId, + itemId } ]; From 5c376e1c000fe70abf7ee2f9f4c4b55203b0977f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 18 Sep 2018 16:43:55 +0200 Subject: [PATCH 20/81] Fix history link for repositories in list view --- scm-ui/src/repos/components/list/RepositoryEntry.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scm-ui/src/repos/components/list/RepositoryEntry.js b/scm-ui/src/repos/components/list/RepositoryEntry.js index bc170144aa..ef1df0d3d9 100644 --- a/scm-ui/src/repos/components/list/RepositoryEntry.js +++ b/scm-ui/src/repos/components/list/RepositoryEntry.js @@ -1,9 +1,9 @@ //@flow import React from "react"; -import { Link } from "react-router-dom"; +import {Link} from "react-router-dom"; import injectSheet from "react-jss"; -import type { Repository } from "@scm-manager/ui-types"; -import { DateFromNow } from "@scm-manager/ui-components"; +import type {Repository} from "@scm-manager/ui-types"; +import {DateFromNow} from "@scm-manager/ui-components"; import RepositoryEntryLink from "./RepositoryEntryLink"; import classNames from "classnames"; import RepositoryAvatar from "./RepositoryAvatar"; @@ -45,7 +45,7 @@ class RepositoryEntry extends React.Component { return ( ); } From 03bfb92ec72929e2691260984570d95e8251a825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 18 Sep 2018 16:45:22 +0200 Subject: [PATCH 21/81] Change changeset list from table to simple divs --- .../changesets/components/ChangesetAvatar.js | 28 ++++++++ .../src/changesets/components/ChangesetRow.js | 71 ++++++++++++++----- .../changesets/components/ChangesetTable.js | 29 ++++---- 3 files changed, 93 insertions(+), 35 deletions(-) create mode 100644 scm-ui/src/changesets/components/ChangesetAvatar.js diff --git a/scm-ui/src/changesets/components/ChangesetAvatar.js b/scm-ui/src/changesets/components/ChangesetAvatar.js new file mode 100644 index 0000000000..eb461d374d --- /dev/null +++ b/scm-ui/src/changesets/components/ChangesetAvatar.js @@ -0,0 +1,28 @@ +//@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"; + +type Props = { + changeset: Changeset +}; + +class ChangesetAvatar extends React.Component { + render() { + const { changeset } = this.props; + return ( +

+ + Logo + +

+ ); + } +} + +export default ChangesetAvatar; diff --git a/scm-ui/src/changesets/components/ChangesetRow.js b/scm-ui/src/changesets/components/ChangesetRow.js index d0be206866..ac6d4ef143 100644 --- a/scm-ui/src/changesets/components/ChangesetRow.js +++ b/scm-ui/src/changesets/components/ChangesetRow.js @@ -1,28 +1,63 @@ -import React from "react" -import type { Changeset } from "@scm-manager/ui-types" -import {ExtensionPoint} from "@scm-manager/ui-extensions"; +//@flow +import React from "react"; +import type { Changeset } from "@scm-manager/ui-types"; +import classNames from "classnames"; +import { Link } from "react-router-dom"; +import ChangesetAvatar from "./ChangesetAvatar"; +import injectSheet from "react-jss"; + +const styles = { + pointer: { + cursor: "pointer" + }, + changesetGroup: { + marginBottom: "1em" + } +}; type Props = { - changeset: Changeset -} + changeset: Changeset, + classes: any +}; class ChangesetRow extends React.Component { + createLink = (changeset: Changeset) => { + return `/repo/${changeset.description}/changeset/${changeset.id}`; + }; render() { - const { changeset } = this.props; + const { changeset, classes } = this.props; + const changesetLink = this.createLink(changeset); // todo: i18n - return - - -

{changeset.description}

-

Changeset { changeset.id } commited at { changeset.date }

-

{changeset.author.name} <{changeset.author.mail}>

- + return ( +
+ + +
+ ); } } -export default ChangesetRow; +export default injectSheet(styles)(ChangesetRow); diff --git a/scm-ui/src/changesets/components/ChangesetTable.js b/scm-ui/src/changesets/components/ChangesetTable.js index 0658e213e3..26895c0324 100644 --- a/scm-ui/src/changesets/components/ChangesetTable.js +++ b/scm-ui/src/changesets/components/ChangesetTable.js @@ -1,29 +1,24 @@ // @flow import ChangesetRow from "./ChangesetRow"; import React from "react"; +import type {Changeset} from "@scm-manager/ui-types"; type Props = { changesets: Changeset[] -} +}; class ChangesetTable extends React.Component { - render() { - const {changesets} = this.props; - return
- - - - - - - - {changesets.map((changeset, index) => { - return ; - })} - -
Changesets
-
+ const { changesets } = this.props; + const content = changesets.map((changeset, index) => { + return ; + }); + return ( +
+
+ {content} +
+ ); } } From dc59d1a5ce07339eb7f4727e78318ee71df53bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 18 Sep 2018 16:51:31 +0200 Subject: [PATCH 22/81] Move
to correct file --- .../changesets/components/ChangesetTable.js | 7 +- .../src/changesets/containers/Changesets.js | 125 ++++++++++++------ 2 files changed, 83 insertions(+), 49 deletions(-) diff --git a/scm-ui/src/changesets/components/ChangesetTable.js b/scm-ui/src/changesets/components/ChangesetTable.js index 26895c0324..2dd0d1b955 100644 --- a/scm-ui/src/changesets/components/ChangesetTable.js +++ b/scm-ui/src/changesets/components/ChangesetTable.js @@ -13,12 +13,7 @@ class ChangesetTable extends React.Component { const content = changesets.map((changeset, index) => { return ; }); - return ( -
-
- {content} -
- ); + return
{content}
; } } diff --git a/scm-ui/src/changesets/containers/Changesets.js b/scm-ui/src/changesets/containers/Changesets.js index 592cc6d9e5..09410e91c0 100644 --- a/scm-ui/src/changesets/containers/Changesets.js +++ b/scm-ui/src/changesets/containers/Changesets.js @@ -1,25 +1,34 @@ -import React from "react" -import {connect} from "react-redux"; -import {ErrorNotification, Loading} from "@scm-manager/ui-components"; +import React from "react"; +import { connect } from "react-redux"; +import { ErrorNotification, Loading } from "@scm-manager/ui-components"; import { - fetchChangesetsByNamespaceAndName, fetchChangesetsByNamespaceNameAndBranch, - getChangesets, getFetchChangesetsFailure, isFetchChangesetsPending, + fetchChangesetsByNamespaceAndName, + fetchChangesetsByNamespaceNameAndBranch, + getChangesets, + getFetchChangesetsFailure, + isFetchChangesetsPending } from "../modules/changesets"; -import type {History} from "history"; -import {fetchBranchesByNamespaceAndName, getBranchNames} from "../../repos/modules/branches"; -import type {Repository} from "@scm-manager/ui-types"; +import type { History } from "history"; +import { + fetchBranchesByNamespaceAndName, + getBranchNames +} from "../../repos/modules/branches"; +import type { Repository } from "@scm-manager/ui-types"; import ChangesetTable from "../components/ChangesetTable"; import DropDown from "../components/DropDown"; -import {withRouter} from "react-router-dom"; +import { withRouter } from "react-router-dom"; type Props = { repository: Repository, branchName: string, history: History, - fetchChangesetsByNamespaceNameAndBranch: (namespace: string, name: string, branch: string) => void -} - + fetchChangesetsByNamespaceNameAndBranch: ( + namespace: string, + name: string, + branch: string + ) => void +}; class Changesets extends React.Component { constructor(props) { @@ -28,9 +37,13 @@ class Changesets extends React.Component { } componentDidMount() { - const {namespace, name} = this.props.repository; + const { namespace, name } = this.props.repository; const branchName = this.props.match.params.branch; - const {fetchChangesetsByNamespaceNameAndBranch, fetchChangesetsByNamespaceAndName, fetchBranchesByNamespaceAndName} = this.props; + const { + fetchChangesetsByNamespaceNameAndBranch, + fetchChangesetsByNamespaceAndName, + fetchBranchesByNamespaceAndName + } = this.props; if (branchName) { fetchChangesetsByNamespaceNameAndBranch(namespace, name, branchName); } else { @@ -40,48 +53,66 @@ class Changesets extends React.Component { } render() { - const {changesets, loading, error} = this.props; + const { changesets, loading, error } = this.props; if (loading || !changesets) { - return + return ; } - return
- - {this.renderContent()} -
- + return ( +
+ + {this.renderContent()} +
+ ); } renderContent = () => { const branch = this.props.match.params.branch; - const {changesets, branchNames} = this.props; + const { changesets, branchNames } = this.props; if (branchNames && branchNames.length > 0) { - return
- this.branchChanged(branch)}/> - -
; + return ( +
+ + this.branchChanged(branch)} + /> +
+ +
+ ); } - return + return ; }; - branchChanged = (branchName: string) => { - const {history, repository} = this.props; - history.push(`/repo/${repository.namespace}/${repository.name}/history/${branchName}`); + const { history, repository } = this.props; + history.push( + `/repo/${repository.namespace}/${repository.name}/history/${branchName}` + ); }; } - const mapStateToProps = (state, ownProps: Props) => { - const {namespace, name} = ownProps.repository; + const { namespace, name } = ownProps.repository; return { loading: isFetchChangesetsPending(namespace, name, state), - changesets: getChangesets(state, namespace, name, ownProps.match.params.branch), + changesets: getChangesets( + state, + namespace, + name, + ownProps.match.params.branch + ), branchNames: getBranchNames(namespace, name, state), - error: getFetchChangesetsFailure(state, namespace, name, ownProps.match.params.branch) - } + error: getFetchChangesetsFailure( + state, + namespace, + name, + ownProps.match.params.branch + ) + }; }; const mapDispatchToProps = dispatch => { @@ -89,16 +120,24 @@ const mapDispatchToProps = dispatch => { fetchChangesetsByNamespaceAndName: (namespace: string, name: string) => { dispatch(fetchChangesetsByNamespaceAndName(namespace, name)); }, - fetchChangesetsByNamespaceNameAndBranch: (namespace: string, name: string, branch: string) => { - dispatch(fetchChangesetsByNamespaceNameAndBranch(namespace, name, branch)); + fetchChangesetsByNamespaceNameAndBranch: ( + namespace: string, + name: string, + branch: string + ) => { + dispatch( + fetchChangesetsByNamespaceNameAndBranch(namespace, name, branch) + ); }, fetchBranchesByNamespaceAndName: (namespace: string, name: string) => { dispatch(fetchBranchesByNamespaceAndName(namespace, name)); } - } + }; }; -export default withRouter(connect( - mapStateToProps, - mapDispatchToProps -)(Changesets)); +export default withRouter( + connect( + mapStateToProps, + mapDispatchToProps + )(Changesets) +); From b308aa32f228d7e2f13f6c6bca4c48f9074ebe61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 18 Sep 2018 17:10:39 +0200 Subject: [PATCH 23/81] Fix dummy link --- scm-ui/src/changesets/components/ChangesetRow.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scm-ui/src/changesets/components/ChangesetRow.js b/scm-ui/src/changesets/components/ChangesetRow.js index ac6d4ef143..1c72300459 100644 --- a/scm-ui/src/changesets/components/ChangesetRow.js +++ b/scm-ui/src/changesets/components/ChangesetRow.js @@ -1,8 +1,8 @@ //@flow import React from "react"; -import type { Changeset } from "@scm-manager/ui-types"; +import type {Changeset} from "@scm-manager/ui-types"; import classNames from "classnames"; -import { Link } from "react-router-dom"; +import {Link} from "react-router-dom"; import ChangesetAvatar from "./ChangesetAvatar"; import injectSheet from "react-jss"; @@ -22,7 +22,7 @@ type Props = { class ChangesetRow extends React.Component { createLink = (changeset: Changeset) => { - return `/repo/${changeset.description}/changeset/${changeset.id}`; + return `/repo/changeset/${changeset.id}`; }; render() { From 6d0146f26721456914ffd7232f84b000afd401c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 19 Sep 2018 09:49:24 +0200 Subject: [PATCH 24/81] Adapt layout to next.scm-manager.org --- scm-ui/public/locales/en/changesets.json | 3 +- .../src/changesets/components/ChangesetRow.js | 44 +++++++++++-------- .../changesets/components/ChangesetTable.js | 5 ++- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/scm-ui/public/locales/en/changesets.json b/scm-ui/public/locales/en/changesets.json index 6628fcf62f..8c804d8f62 100644 --- a/scm-ui/public/locales/en/changesets.json +++ b/scm-ui/public/locales/en/changesets.json @@ -3,7 +3,8 @@ "id": "ID", "description": "Description", "contact": "Contact", - "date": "Date" + "date": "Date", + "summary": "Changeset {{id}} committed at {{time}}" }, "author": { "name": "Author", diff --git a/scm-ui/src/changesets/components/ChangesetRow.js b/scm-ui/src/changesets/components/ChangesetRow.js index 1c72300459..9892b0eb34 100644 --- a/scm-ui/src/changesets/components/ChangesetRow.js +++ b/scm-ui/src/changesets/components/ChangesetRow.js @@ -2,7 +2,7 @@ import React from "react"; import type {Changeset} from "@scm-manager/ui-types"; import classNames from "classnames"; -import {Link} from "react-router-dom"; +import {translate} from "react-i18next"; import ChangesetAvatar from "./ChangesetAvatar"; import injectSheet from "react-jss"; @@ -17,6 +17,7 @@ const styles = { type Props = { changeset: Changeset, + t: any, classes: any }; @@ -26,38 +27,43 @@ class ChangesetRow extends React.Component { }; render() { - const { changeset, classes } = this.props; + const { changeset, t, classes } = this.props; const changesetLink = this.createLink(changeset); + const authorLine = ( + <> + {changeset.author.name}{" "} + + < + {changeset.author.mail} + > + + + ); // todo: i18n return ( -
-
-

-

- Changeset {changeset.id} commited at {changeset.date} -

-

- {changeset.author.name}{" "} - - < - {changeset.author.mail} - > - -

-

{changeset.description}

+

+ {changeset.description} +
+ {t("changeset.summary", { + id: changeset.id, + time: changeset.date + })}

+

{authorLine}

-
); } } -export default injectSheet(styles)(ChangesetRow); +export default injectSheet(styles)(translate("changesets")(ChangesetRow)); diff --git a/scm-ui/src/changesets/components/ChangesetTable.js b/scm-ui/src/changesets/components/ChangesetTable.js index 2dd0d1b955..88c2b2fc3a 100644 --- a/scm-ui/src/changesets/components/ChangesetTable.js +++ b/scm-ui/src/changesets/components/ChangesetTable.js @@ -1,7 +1,8 @@ // @flow import ChangesetRow from "./ChangesetRow"; import React from "react"; -import type {Changeset} from "@scm-manager/ui-types"; +import type { Changeset } from "@scm-manager/ui-types"; +import classNames from "classnames"; type Props = { changesets: Changeset[] @@ -13,7 +14,7 @@ class ChangesetTable extends React.Component { const content = changesets.map((changeset, index) => { return ; }); - return
{content}
; + return
{content}
; } } From 10b120c86250fd6f88bb1edd380a6e874197a55a Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Wed, 19 Sep 2018 13:49:04 +0200 Subject: [PATCH 25/81] Restructured changeset module --- .../src/changesets/containers/Changesets.js | 70 ++++-- scm-ui/src/changesets/modules/changesets.js | 218 ++++++++++++++---- .../src/changesets/modules/changesets.test.js | 175 ++++++++++---- 3 files changed, 352 insertions(+), 111 deletions(-) diff --git a/scm-ui/src/changesets/containers/Changesets.js b/scm-ui/src/changesets/containers/Changesets.js index 09410e91c0..5f0d6d9486 100644 --- a/scm-ui/src/changesets/containers/Changesets.js +++ b/scm-ui/src/changesets/containers/Changesets.js @@ -1,20 +1,26 @@ +// @flow import React from "react"; import { connect } from "react-redux"; -import { ErrorNotification, Loading } from "@scm-manager/ui-components"; +import { + ErrorNotification, + Loading, + Paginator +} from "@scm-manager/ui-components"; import { - fetchChangesetsByNamespaceAndName, + fetchChangesets, fetchChangesetsByNamespaceNameAndBranch, getChangesets, getFetchChangesetsFailure, - isFetchChangesetsPending + isFetchChangesetsPending, + selectListAsCollection } from "../modules/changesets"; import type { History } from "history"; import { fetchBranchesByNamespaceAndName, getBranchNames } from "../../repos/modules/branches"; -import type { Repository } from "@scm-manager/ui-types"; +import type { PagedCollection, Repository } from "@scm-manager/ui-types"; import ChangesetTable from "../components/ChangesetTable"; import DropDown from "../components/DropDown"; import { withRouter } from "react-router-dom"; @@ -27,7 +33,8 @@ type Props = { namespace: string, name: string, branch: string - ) => void + ) => void, + list: PagedCollection }; class Changesets extends React.Component { @@ -60,12 +67,13 @@ class Changesets extends React.Component { return (
- {this.renderContent()} + {this.renderTable()} + {this.renderPaginator()}
); } - renderContent = () => { + renderTable = () => { const branch = this.props.match.params.branch; const { changesets, branchNames } = this.props; @@ -78,7 +86,6 @@ class Changesets extends React.Component { preselectedOption={branch} optionSelected={branch => this.branchChanged(branch)} /> -
); @@ -87,7 +94,15 @@ class Changesets extends React.Component { return ; }; - branchChanged = (branchName: string) => { + renderPaginator() { + const { list } = this.props; + if (list) { + return ; + } + return null; + } + + branchChanged = (branchName: string): void => { const { history, repository } = this.props; history.push( `/repo/${repository.namespace}/${repository.name}/history/${branchName}` @@ -97,28 +112,35 @@ class Changesets extends React.Component { const mapStateToProps = (state, ownProps: Props) => { const { namespace, name } = ownProps.repository; + const loading = isFetchChangesetsPending(namespace, name, state); + const changesets = getChangesets( + state, + namespace, + name, + ownProps.match.params.branch + ); + const branchNames = getBranchNames(namespace, name, state); + const error = getFetchChangesetsFailure( + state, + namespace, + name, + ownProps.match.params.branch + ); + const list = selectListAsCollection(state); + return { - loading: isFetchChangesetsPending(namespace, name, state), - changesets: getChangesets( - state, - namespace, - name, - ownProps.match.params.branch - ), - branchNames: getBranchNames(namespace, name, state), - error: getFetchChangesetsFailure( - state, - namespace, - name, - ownProps.match.params.branch - ) + loading, + changesets, + branchNames, + error, + list }; }; const mapDispatchToProps = dispatch => { return { fetchChangesetsByNamespaceAndName: (namespace: string, name: string) => { - dispatch(fetchChangesetsByNamespaceAndName(namespace, name)); + dispatch(fetchChangesets(namespace, name)); }, fetchChangesetsByNamespaceNameAndBranch: ( namespace: string, diff --git a/scm-ui/src/changesets/modules/changesets.js b/scm-ui/src/changesets/modules/changesets.js index 6423dca515..b806fff535 100644 --- a/scm-ui/src/changesets/modules/changesets.js +++ b/scm-ui/src/changesets/modules/changesets.js @@ -1,9 +1,15 @@ // @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 { + 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, PagedCollection } from "@scm-manager/ui-types"; export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS"; export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; @@ -11,51 +17,98 @@ export const FETCH_CHANGESETS_SUCCESS = `${FETCH_CHANGESETS}_${SUCCESS_SUFFIX}`; export const FETCH_CHANGESETS_FAILURE = `${FETCH_CHANGESETS}_${FAILURE_SUFFIX}`; const REPO_URL = "repositories"; - - +//TODO: Content type // actions -export function fetchChangesetsByNamespaceAndName(namespace: string, name: string) { - return function (dispatch: any) { - dispatch(fetchChangesetsPending(namespace, name)); - return apiClient.get(REPO_URL + "/" + namespace + "/" + name + "/changesets").then(response => response.json()) - .then(data => { - dispatch(fetchChangesetsSuccess(data, namespace, name)) - }).catch(cause => { - dispatch(fetchChangesetsFailure(namespace, name, cause)) - }) - } -} -export function fetchChangesetsByNamespaceNameAndBranch(namespace: string, name: string, branch: string) { - return function (dispatch: any) { +export function fetchChangesetsWithOptions( + namespace: string, + name: string, + branch?: string, + suffix?: string +) { + let link = REPO_URL + `/${namespace}/${name}`; + if (branch && branch !== "") { + link = link + `/branches/${branch}`; + } + link = link + "/changesets"; + if (suffix) { + link = link + `${suffix}`; + } + return function(dispatch: any) { dispatch(fetchChangesetsPending(namespace, name, branch)); - return apiClient.get(REPO_URL + "/" + namespace + "/" + name + "/branches/" + branch + "/changesets").then(response => response.json()) + return apiClient + .get(link) + .then(response => response.json()) .then(data => { - dispatch(fetchChangesetsSuccess(data, namespace, name, branch)) - }).catch(cause => { - dispatch(fetchChangesetsFailure(namespace, name, branch, cause)) + dispatch(fetchChangesetsSuccess(data, namespace, name, branch)); }) - } + .catch(cause => { + dispatch(fetchChangesetsFailure(namespace, name, cause, branch)); + }); + }; } -export function fetchChangesetsPending(namespace: string, name: string, branch?: string): Action { +export function fetchChangesets(namespace: string, name: string) { + return fetchChangesetsWithOptions(namespace, name); +} + +export function fetchChangesetsByPage( + namespace: string, + name: string, + page: number +) { + return fetchChangesetsWithOptions(namespace, name, "", `?page=${page}`); +} + +export function fetchChangesetsByBranchAndPage( + namespace: string, + name: string, + branch: string, + page: number +) { + return fetchChangesetsWithOptions(namespace, name, branch, `?page=${page}`); +} + +export function fetchChangesetsByNamespaceNameAndBranch( + namespace: string, + name: string, + branch: string +) { + return fetchChangesetsWithOptions(namespace, name, branch); +} + +export function fetchChangesetsPending( + namespace: string, + name: string, + branch?: string +): Action { const itemId = createItemId(namespace, name, branch); return { type: FETCH_CHANGESETS_PENDING, payload: itemId, itemId - } + }; } -export function fetchChangesetsSuccess(changesets: any, namespace: string, name: string, branch?: string): Action { +export function fetchChangesetsSuccess( + changesets: any, + namespace: string, + name: string, + branch?: string +): Action { return { type: FETCH_CHANGESETS_SUCCESS, payload: changesets, itemId: createItemId(namespace, name, branch) - } + }; } -function fetchChangesetsFailure(namespace: string, name: string, branch?: string, error: Error): Action { +function fetchChangesetsFailure( + namespace: string, + name: string, + error: Error, + branch?: string +): Action { return { type: FETCH_CHANGESETS_FAILURE, payload: { @@ -65,10 +118,14 @@ function fetchChangesetsFailure(namespace: string, name: string, branch?: string error }, itemId: createItemId(namespace, name, branch) - } + }; } -function createItemId(namespace: string, name: string, branch?: string): string { +function createItemId( + namespace: string, + name: string, + branch?: string +): string { let itemId = namespace + "/" + name; if (branch && branch !== "") { itemId = itemId + "/" + branch; @@ -77,20 +134,54 @@ function createItemId(namespace: string, name: string, branch?: string): string } // reducer -export default function reducer(state: any = {}, action: Action = {type: "UNKNOWN"}): Object { +function byKeyReducer( + state: any = {}, + action: Action = { type: "UNKNOWN" } +): Object { switch (action.type) { case FETCH_CHANGESETS_SUCCESS: - const key = action.itemId - let oldChangesets = {[key]: {}}; + const key = action.itemId; + let oldChangesets = { [key]: {} }; if (state[key] !== undefined) { - oldChangesets[key] = state[key] + oldChangesets[key] = state[key]; } - return {...state, [key]: {byId: extractChangesetsByIds(action.payload, 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 + } + }; + default: + return state; + } +} + +export default combineReducers({ + list: listReducer, + byKey: byKeyReducer +}); + function extractChangesetsByIds(data: any, oldChangesetsByIds: any) { const changesets = data._embedded.changesets; const changesetsByIds = {}; @@ -107,19 +198,60 @@ function extractChangesetsByIds(data: any, oldChangesetsByIds: any) { } //selectors -export function getChangesets(state: Object, namespace: string, name: string, branch?: string) { +export function getChangesets( + state: Object, + namespace: string, + name: string, + branch?: string +) { const key = createItemId(namespace, name, branch); - if (!state.changesets[key]) { + if (!state.changesets.byKey[key]) { return null; } - return Object.values(state.changesets[key].byId); + return Object.values(state.changesets.byKey[key].byId); } -export function isFetchChangesetsPending(state: Object, namespace: string, name: string, branch?: string) { - return isPending(state, FETCH_CHANGESETS, createItemId(namespace, name, branch)) +export function isFetchChangesetsPending( + state: Object, + namespace: string, + name: string, + branch?: string +) { + return isPending( + state, + FETCH_CHANGESETS, + createItemId(namespace, name, branch) + ); } -export function getFetchChangesetsFailure(state: Object, namespace: string, name: string, branch?: string) { - return getFailure(state, FETCH_CHANGESETS, createItemId(namespace, name, branch)); +export function getFetchChangesetsFailure( + state: Object, + namespace: string, + name: string, + branch?: string +) { + return getFailure( + state, + FETCH_CHANGESETS, + createItemId(namespace, name, branch) + ); } +const selectList = (state: Object) => { + if (state.changesets && state.changesets.list) { + return state.changesets.list; + } + return {}; +}; + +const selectListEntry = (state: Object): Object => { + const list = selectList(state); + if (list.entry) { + return list.entry; + } + return {}; +}; + +export const selectListAsCollection = (state: Object): PagedCollection => { + return selectListEntry(state); +}; diff --git a/scm-ui/src/changesets/modules/changesets.test.js b/scm-ui/src/changesets/modules/changesets.test.js index c74eee3975..d46dff8ef7 100644 --- a/scm-ui/src/changesets/modules/changesets.test.js +++ b/scm-ui/src/changesets/modules/changesets.test.js @@ -8,8 +8,10 @@ import { FETCH_CHANGESETS_FAILURE, FETCH_CHANGESETS_PENDING, FETCH_CHANGESETS_SUCCESS, - fetchChangesetsByNamespaceAndName, + fetchChangesets, + fetchChangesetsByBranchAndPage, fetchChangesetsByNamespaceNameAndBranch, + fetchChangesetsByPage, fetchChangesetsSuccess, getChangesets, getFetchChangesetsFailure, @@ -22,7 +24,8 @@ const changesets = {}; describe("changesets", () => { describe("fetching of changesets", () => { const DEFAULT_BRANCH_URL = "/api/rest/v2/repositories/foo/bar/changesets"; - const SPECIFIC_BRANCH_URL = "/api/rest/v2/repositories/foo/bar/branches/specific/changesets"; + const SPECIFIC_BRANCH_URL = + "/api/rest/v2/repositories/foo/bar/branches/specific/changesets"; const mockStore = configureMockStore([thunk]); afterEach(() => { @@ -35,7 +38,8 @@ describe("changesets", () => { const expectedActions = [ { - type: FETCH_CHANGESETS_PENDING, payload: "foo/bar", + type: FETCH_CHANGESETS_PENDING, + payload: "foo/bar", itemId: "foo/bar" }, { @@ -46,7 +50,7 @@ describe("changesets", () => { ]; const store = mockStore({}); - return store.dispatch(fetchChangesetsByNamespaceAndName("foo", "bar")).then(() => { + return store.dispatch(fetchChangesets("foo", "bar")).then(() => { expect(store.getActions()).toEqual(expectedActions); }); }); @@ -57,7 +61,8 @@ describe("changesets", () => { const expectedActions = [ { - type: FETCH_CHANGESETS_PENDING, payload: itemId, + type: FETCH_CHANGESETS_PENDING, + payload: itemId, itemId }, { @@ -68,9 +73,13 @@ describe("changesets", () => { ]; const store = mockStore({}); - return store.dispatch(fetchChangesetsByNamespaceNameAndBranch("foo", "bar", "specific")).then(() => { - expect(store.getActions()).toEqual(expectedActions); - }); + return store + .dispatch( + fetchChangesetsByNamespaceNameAndBranch("foo", "bar", "specific") + ) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); }); it("should fail fetching changesets on error", () => { @@ -79,18 +88,19 @@ describe("changesets", () => { const expectedActions = [ { - type: FETCH_CHANGESETS_PENDING, payload: itemId, + type: FETCH_CHANGESETS_PENDING, + payload: itemId, itemId } ]; const store = mockStore({}); - return store.dispatch(fetchChangesetsByNamespaceAndName("foo", "bar")).then(() => { + return store.dispatch(fetchChangesets("foo", "bar")).then(() => { expect(store.getActions()[0]).toEqual(expectedActions[0]); expect(store.getActions()[1].type).toEqual(FETCH_CHANGESETS_FAILURE); expect(store.getActions()[1].payload).toBeDefined(); }); - }) + }); it("should fail fetching changesets for specific branch on error", () => { const itemId = "foo/bar/specific"; @@ -98,27 +108,81 @@ describe("changesets", () => { const expectedActions = [ { - type: FETCH_CHANGESETS_PENDING, payload: itemId, + type: FETCH_CHANGESETS_PENDING, + payload: itemId, itemId } ]; const store = mockStore({}); - return store.dispatch(fetchChangesetsByNamespaceNameAndBranch("foo", "bar", "specific")).then(() => { - expect(store.getActions()[0]).toEqual(expectedActions[0]); - expect(store.getActions()[1].type).toEqual(FETCH_CHANGESETS_FAILURE); - expect(store.getActions()[1].payload).toBeDefined(); + return store + .dispatch( + fetchChangesetsByNamespaceNameAndBranch("foo", "bar", "specific") + ) + .then(() => { + expect(store.getActions()[0]).toEqual(expectedActions[0]); + expect(store.getActions()[1].type).toEqual(FETCH_CHANGESETS_FAILURE); + expect(store.getActions()[1].payload).toBeDefined(); + }); + }); + + it("should fetch changesets by page", () => { + fetchMock.getOnce(DEFAULT_BRANCH_URL + "?page=5", "{}"); + + const expectedActions = [ + { + type: FETCH_CHANGESETS_PENDING, + payload: "foo/bar", + itemId: "foo/bar" + }, + { + type: FETCH_CHANGESETS_SUCCESS, + payload: changesets, + itemId: "foo/bar" + } + ]; + + const store = mockStore({}); + return store.dispatch(fetchChangesetsByPage("foo", "bar", 5)).then(() => { + expect(store.getActions()).toEqual(expectedActions); }); - }) + }); + + it("should fetch changesets by branch and page", () => { + fetchMock.getOnce(SPECIFIC_BRANCH_URL + "?page=5", "{}"); + + const expectedActions = [ + { + type: FETCH_CHANGESETS_PENDING, + payload: "foo/bar/specific", + itemId: "foo/bar/specific" + }, + { + type: FETCH_CHANGESETS_SUCCESS, + payload: changesets, + itemId: "foo/bar/specific" + } + ]; + + const store = mockStore({}); + return store + .dispatch(fetchChangesetsByBranchAndPage("foo", "bar", "specific", 5)) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); + }); }); describe("changesets reducer", () => { const responseBody = { + page: 1, + pageTotal: 10, + _links: {}, _embedded: { changesets: [ - {id: "changeset1", author: {mail: "z@phod.com", name: "zaphod"}}, - {id: "changeset2", description: "foo"}, - {id: "changeset3", description: "bar"}, + { id: "changeset1", author: { mail: "z@phod.com", name: "zaphod" } }, + { id: "changeset2", description: "foo" }, + { id: "changeset3", description: "bar" } ], _embedded: { tags: [], @@ -129,18 +193,35 @@ describe("changesets", () => { }; it("should set state to received changesets", () => { - const newState = reducer({}, fetchChangesetsSuccess(responseBody, "foo", "bar")); + const newState = reducer( + {}, + fetchChangesetsSuccess(responseBody, "foo", "bar") + ); expect(newState).toBeDefined(); - expect(newState["foo/bar"].byId["changeset1"].author.mail).toEqual("z@phod.com"); - expect(newState["foo/bar"].byId["changeset2"].description).toEqual("foo"); - expect(newState["foo/bar"].byId["changeset3"].description).toEqual("bar"); + expect(newState.byKey["foo/bar"].byId["changeset1"].author.mail).toEqual( + "z@phod.com" + ); + expect(newState.byKey["foo/bar"].byId["changeset2"].description).toEqual( + "foo" + ); + expect(newState.byKey["foo/bar"].byId["changeset3"].description).toEqual( + "bar" + ); + expect(newState.list).toEqual({ + entry: { + page: 1, + pageTotal: 10, + _links: {} + }, + 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"}}, + { id: "changeset1", author: { mail: "z@phod.com", name: "zaphod" } } ], _embedded: { tags: [], @@ -149,19 +230,24 @@ describe("changesets", () => { } } }; - const newState = reducer({ - "foo/bar": { - byId: { - ["changeset2"]: { - id: "changeset2", - author: {mail: "mail@author.com", name: "author"} + const newState = reducer( + { + byKey: { + "foo/bar": { + byId: { + ["changeset2"]: { + id: "changeset2", + author: { mail: "mail@author.com", name: "author" } + } + } } } - } - }, fetchChangesetsSuccess(responseBody, "foo", "bar")); - expect(newState["foo/bar"].byId["changeset2"]).toBeDefined(); - expect(newState["foo/bar"].byId["changeset1"]).toBeDefined(); - }) + }, + fetchChangesetsSuccess(responseBody, "foo", "bar") + ); + expect(newState.byKey["foo/bar"].byId["changeset2"]).toBeDefined(); + expect(newState.byKey["foo/bar"].byId["changeset1"]).toBeDefined(); + }); }); describe("changeset selectors", () => { @@ -170,16 +256,18 @@ describe("changesets", () => { it("should get all changesets for a given namespace and name", () => { const state = { changesets: { - ["foo/bar"]: { - byId: { - "id1": {id: "id1"}, - "id2": {id: "id2"} + byKey: { + "foo/bar": { + byId: { + id1: { id: "id1" }, + id2: { id: "id2" } + } } } } }; - const result = getChangesets(state, "foo", "bar" ); - expect(result).toContainEqual({id: "id1"}) + const result = getChangesets(state, "foo", "bar"); + expect(result).toContainEqual({ id: "id1" }); }); it("should return true, when fetching changesets is pending", () => { @@ -208,7 +296,6 @@ describe("changesets", () => { it("should return false if fetching changesets did not fail", () => { expect(getFetchChangesetsFailure({}, "foo", "bar")).toBeUndefined(); - }) - + }); }); }); From c853a3eb80875a74c62781a53b49b4e79e6f3033 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Wed, 19 Sep 2018 14:10:50 +0200 Subject: [PATCH 26/81] Moved changeset code to repos --- scm-ui/src/createReduxStore.js | 2 +- .../components/ChangesetAvatar.js | 0 .../components/ChangesetRow.js | 38 +++++++++---------- .../components/ChangesetTable.js | 0 .../components/DropDown.js | 0 .../containers/Changesets.js | 0 scm-ui/src/repos/containers/RepositoryRoot.js | 2 +- .../modules/changesets.js | 0 .../modules/changesets.test.js | 0 9 files changed, 21 insertions(+), 21 deletions(-) rename scm-ui/src/{changesets => repos}/components/ChangesetAvatar.js (100%) rename scm-ui/src/{changesets => repos}/components/ChangesetRow.js (58%) rename scm-ui/src/{changesets => repos}/components/ChangesetTable.js (100%) rename scm-ui/src/{changesets => repos}/components/DropDown.js (100%) rename scm-ui/src/{changesets => repos}/containers/Changesets.js (100%) rename scm-ui/src/{changesets => repos}/modules/changesets.js (100%) rename scm-ui/src/{changesets => repos}/modules/changesets.test.js (100%) diff --git a/scm-ui/src/createReduxStore.js b/scm-ui/src/createReduxStore.js index 5c4fb20410..c35898ddbf 100644 --- a/scm-ui/src/createReduxStore.js +++ b/scm-ui/src/createReduxStore.js @@ -7,7 +7,7 @@ import { routerReducer, routerMiddleware } from "react-router-redux"; import users from "./users/modules/users"; import repos from "./repos/modules/repos"; import repositoryTypes from "./repos/modules/repositoryTypes"; -import changesets from "./changesets/modules/changesets"; +import changesets from "./repos/modules/changesets"; import groups from "./groups/modules/groups"; import auth from "./modules/auth"; import pending from "./modules/pending"; diff --git a/scm-ui/src/changesets/components/ChangesetAvatar.js b/scm-ui/src/repos/components/ChangesetAvatar.js similarity index 100% rename from scm-ui/src/changesets/components/ChangesetAvatar.js rename to scm-ui/src/repos/components/ChangesetAvatar.js diff --git a/scm-ui/src/changesets/components/ChangesetRow.js b/scm-ui/src/repos/components/ChangesetRow.js similarity index 58% rename from scm-ui/src/changesets/components/ChangesetRow.js rename to scm-ui/src/repos/components/ChangesetRow.js index 9892b0eb34..e6a69fef4b 100644 --- a/scm-ui/src/changesets/components/ChangesetRow.js +++ b/scm-ui/src/repos/components/ChangesetRow.js @@ -1,8 +1,8 @@ //@flow import React from "react"; -import type {Changeset} from "@scm-manager/ui-types"; +import type { Changeset } from "@scm-manager/ui-types"; import classNames from "classnames"; -import {translate} from "react-i18next"; +import { translate } from "react-i18next"; import ChangesetAvatar from "./ChangesetAvatar"; import injectSheet from "react-jss"; @@ -44,24 +44,24 @@ class ChangesetRow extends React.Component { ); // todo: i18n return ( -
-
- -
-
-
-

- {changeset.description} -
- {t("changeset.summary", { - id: changeset.id, - time: changeset.date - })} -

-

{authorLine}

-
+
+
+ +
+
+
+

+ {changeset.description} +
+ {t("changeset.summary", { + id: changeset.id, + time: changeset.date + })} +

+

{authorLine}

-
+
+
); } } diff --git a/scm-ui/src/changesets/components/ChangesetTable.js b/scm-ui/src/repos/components/ChangesetTable.js similarity index 100% rename from scm-ui/src/changesets/components/ChangesetTable.js rename to scm-ui/src/repos/components/ChangesetTable.js diff --git a/scm-ui/src/changesets/components/DropDown.js b/scm-ui/src/repos/components/DropDown.js similarity index 100% rename from scm-ui/src/changesets/components/DropDown.js rename to scm-ui/src/repos/components/DropDown.js diff --git a/scm-ui/src/changesets/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js similarity index 100% rename from scm-ui/src/changesets/containers/Changesets.js rename to scm-ui/src/repos/containers/Changesets.js diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 958626ce62..e1352fba31 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -25,7 +25,7 @@ import Edit from "../containers/Edit"; import type {History} from "history"; import EditNavLink from "../components/EditNavLink"; -import Changesets from "../../changesets/containers/Changesets"; +import Changesets from "./Changesets"; type Props = { namespace: string, diff --git a/scm-ui/src/changesets/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js similarity index 100% rename from scm-ui/src/changesets/modules/changesets.js rename to scm-ui/src/repos/modules/changesets.js diff --git a/scm-ui/src/changesets/modules/changesets.test.js b/scm-ui/src/repos/modules/changesets.test.js similarity index 100% rename from scm-ui/src/changesets/modules/changesets.test.js rename to scm-ui/src/repos/modules/changesets.test.js From 66bba0d9cfdd5dc7e7144a04fef38cfac3e15324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 19 Sep 2018 14:15:15 +0200 Subject: [PATCH 27/81] Add link to changeset id and use date from now --- .../src/changesets/components/ChangesetRow.js | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/scm-ui/src/changesets/components/ChangesetRow.js b/scm-ui/src/changesets/components/ChangesetRow.js index 9892b0eb34..cb8691206a 100644 --- a/scm-ui/src/changesets/components/ChangesetRow.js +++ b/scm-ui/src/changesets/components/ChangesetRow.js @@ -1,10 +1,11 @@ //@flow import React from "react"; -import type {Changeset} from "@scm-manager/ui-types"; +import type { Changeset } from "@scm-manager/ui-types"; import classNames from "classnames"; -import {translate} from "react-i18next"; +import { translate, Interpolate } from "react-i18next"; import ChangesetAvatar from "./ChangesetAvatar"; import injectSheet from "react-jss"; +import { DateFromNow } from "@scm-manager/ui-components"; const styles = { pointer: { @@ -23,12 +24,13 @@ type Props = { class ChangesetRow extends React.Component { createLink = (changeset: Changeset) => { - return `/repo/changeset/${changeset.id}`; + return {changeset.id}; }; render() { const { changeset, t, classes } = this.props; const changesetLink = this.createLink(changeset); + const dateFromNow = ; const authorLine = ( <> {changeset.author.name}{" "} @@ -42,26 +44,26 @@ class ChangesetRow extends React.Component { ); - // todo: i18n return ( -
-
- -
-
-
-

- {changeset.description} -
- {t("changeset.summary", { - id: changeset.id, - time: changeset.date - })} -

-

{authorLine}

-
+
+
+ +
+
+
+

+ {changeset.description} +
+ +

{" "} +

{authorLine}

-
+
+
); } } From ea34f7e6b8ef6c416c1331da4768f89ce8ba429c Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Wed, 19 Sep 2018 14:46:01 +0200 Subject: [PATCH 28/81] Fixed displaying of loading state --- scm-ui/src/repos/containers/Changesets.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index 5f0d6d9486..21c8211d90 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -43,6 +43,8 @@ class Changesets extends React.Component { this.state = {}; } + onPageChange = (link: string) => { + }; componentDidMount() { const { namespace, name } = this.props.repository; const branchName = this.props.match.params.branch; @@ -112,7 +114,12 @@ class Changesets extends React.Component { const mapStateToProps = (state, ownProps: Props) => { const { namespace, name } = ownProps.repository; - const loading = isFetchChangesetsPending(namespace, name, state); + const loading = isFetchChangesetsPending( + state, + namespace, + name, + ownProps.match.params.branch + ); const changesets = getChangesets( state, namespace, From 95762c3afa8c0aefe89b8895e34eae77e13f556f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 19 Sep 2018 14:59:00 +0200 Subject: [PATCH 29/81] Fix overflow in media content --- scm-ui/styles/scm.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scm-ui/styles/scm.scss b/scm-ui/styles/scm.scss index 43701de5eb..19c19b607c 100644 --- a/scm-ui/styles/scm.scss +++ b/scm-ui/styles/scm.scss @@ -11,6 +11,10 @@ $blue: #33B2E8; white-space: nowrap; } +.media-content { + overflow: auto; +} + .has-rounded-border { border-radius: 0.25rem; } From 67e140cd247789e2534d6cefb974a7afa9332471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 19 Sep 2018 15:46:47 +0200 Subject: [PATCH 30/81] Do not extend bulmar native css classes --- scm-ui/src/repos/components/ChangesetRow.js | 5 ++++- scm-ui/styles/scm.scss | 4 ---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/scm-ui/src/repos/components/ChangesetRow.js b/scm-ui/src/repos/components/ChangesetRow.js index cb8691206a..cd2e8a228e 100644 --- a/scm-ui/src/repos/components/ChangesetRow.js +++ b/scm-ui/src/repos/components/ChangesetRow.js @@ -13,6 +13,9 @@ const styles = { }, changesetGroup: { marginBottom: "1em" + }, + withOverflow: { + overflow: "auto" } }; @@ -49,7 +52,7 @@ class ChangesetRow extends React.Component {
-
+

{changeset.description} diff --git a/scm-ui/styles/scm.scss b/scm-ui/styles/scm.scss index 19c19b607c..43701de5eb 100644 --- a/scm-ui/styles/scm.scss +++ b/scm-ui/styles/scm.scss @@ -11,10 +11,6 @@ $blue: #33B2E8; white-space: nowrap; } -.media-content { - overflow: auto; -} - .has-rounded-border { border-radius: 0.25rem; } From 231dcbb9cbcc69689d7193750f4f4a4b3fe4f09b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 19 Sep 2018 16:05:35 +0200 Subject: [PATCH 31/81] Correct link to changeset --- scm-ui/src/repos/components/ChangesetRow.js | 15 +++++++++++++-- scm-ui/src/repos/components/ChangesetTable.js | 7 ++++--- scm-ui/src/repos/containers/Changesets.js | 4 ++-- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/scm-ui/src/repos/components/ChangesetRow.js b/scm-ui/src/repos/components/ChangesetRow.js index cd2e8a228e..27830cbb44 100644 --- a/scm-ui/src/repos/components/ChangesetRow.js +++ b/scm-ui/src/repos/components/ChangesetRow.js @@ -1,11 +1,12 @@ //@flow import React from "react"; -import type { Changeset } from "@scm-manager/ui-types"; +import type { Changeset, Repository } from "@scm-manager/ui-types"; import classNames from "classnames"; import { translate, Interpolate } from "react-i18next"; import ChangesetAvatar from "./ChangesetAvatar"; import injectSheet from "react-jss"; import { DateFromNow } from "@scm-manager/ui-components"; +import { Link } from "react-router-dom"; const styles = { pointer: { @@ -20,6 +21,7 @@ const styles = { }; type Props = { + repository: Repository, changeset: Changeset, t: any, classes: any @@ -27,7 +29,16 @@ type Props = { class ChangesetRow extends React.Component { createLink = (changeset: Changeset) => { - return {changeset.id}; + const { repository } = this.props; + return ( + + {changeset.id} + + ); }; render() { diff --git a/scm-ui/src/repos/components/ChangesetTable.js b/scm-ui/src/repos/components/ChangesetTable.js index 88c2b2fc3a..e16ff384c4 100644 --- a/scm-ui/src/repos/components/ChangesetTable.js +++ b/scm-ui/src/repos/components/ChangesetTable.js @@ -1,18 +1,19 @@ // @flow import ChangesetRow from "./ChangesetRow"; import React from "react"; -import type { Changeset } from "@scm-manager/ui-types"; +import type { Changeset, Repository } from "@scm-manager/ui-types"; import classNames from "classnames"; type Props = { + repository: Repository, changesets: Changeset[] }; class ChangesetTable extends React.Component { render() { - const { changesets } = this.props; + const { repository, changesets } = this.props; const content = changesets.map((changeset, index) => { - return ; + return ; }); return

{content}
; } diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index 21c8211d90..e0ab5ee560 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -77,7 +77,7 @@ class Changesets extends React.Component { renderTable = () => { const branch = this.props.match.params.branch; - const { changesets, branchNames } = this.props; + const { repository, changesets, branchNames } = this.props; if (branchNames && branchNames.length > 0) { return ( @@ -88,7 +88,7 @@ class Changesets extends React.Component { preselectedOption={branch} optionSelected={branch => this.branchChanged(branch)} /> - +
); } From 0b6feb6503e28c6c002039a0068bd2c7890d3e92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 19 Sep 2018 16:19:59 +0200 Subject: [PATCH 32/81] Rename ChangesetTable -> ChangesetList --- .../components/{ChangesetTable.js => ChangesetList.js} | 4 ++-- scm-ui/src/repos/containers/Changesets.js | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) rename scm-ui/src/repos/components/{ChangesetTable.js => ChangesetList.js} (86%) diff --git a/scm-ui/src/repos/components/ChangesetTable.js b/scm-ui/src/repos/components/ChangesetList.js similarity index 86% rename from scm-ui/src/repos/components/ChangesetTable.js rename to scm-ui/src/repos/components/ChangesetList.js index e16ff384c4..b5613b3754 100644 --- a/scm-ui/src/repos/components/ChangesetTable.js +++ b/scm-ui/src/repos/components/ChangesetList.js @@ -9,7 +9,7 @@ type Props = { changesets: Changeset[] }; -class ChangesetTable extends React.Component { +class ChangesetList extends React.Component { render() { const { repository, changesets } = this.props; const content = changesets.map((changeset, index) => { @@ -19,4 +19,4 @@ class ChangesetTable extends React.Component { } } -export default ChangesetTable; +export default ChangesetList; diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index e0ab5ee560..5d151e33dd 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -21,7 +21,7 @@ import { getBranchNames } from "../../repos/modules/branches"; import type { PagedCollection, Repository } from "@scm-manager/ui-types"; -import ChangesetTable from "../components/ChangesetTable"; +import ChangesetList from "../components/ChangesetList"; import DropDown from "../components/DropDown"; import { withRouter } from "react-router-dom"; @@ -43,8 +43,7 @@ class Changesets extends React.Component { this.state = {}; } - onPageChange = (link: string) => { - }; + onPageChange = (link: string) => {}; componentDidMount() { const { namespace, name } = this.props.repository; const branchName = this.props.match.params.branch; @@ -88,12 +87,12 @@ class Changesets extends React.Component { preselectedOption={branch} optionSelected={branch => this.branchChanged(branch)} /> - +
); } - return ; + return ; }; renderPaginator() { From dfdcd197db09fc22e60d801db957f2c1fc677e23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 19 Sep 2018 16:21:56 +0200 Subject: [PATCH 33/81] Show only first seven chars of commit id --- scm-ui/src/repos/components/ChangesetRow.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-ui/src/repos/components/ChangesetRow.js b/scm-ui/src/repos/components/ChangesetRow.js index 27830cbb44..a80b8d2b11 100644 --- a/scm-ui/src/repos/components/ChangesetRow.js +++ b/scm-ui/src/repos/components/ChangesetRow.js @@ -36,7 +36,7 @@ class ChangesetRow extends React.Component { changeset.id }`} > - {changeset.id} + {changeset.id.substr(0, 7)} ); }; From d8c5828987bef1cdc843c53b671e36066c0c51eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 19 Sep 2018 16:39:29 +0200 Subject: [PATCH 34/81] Move changeset id renderer to component --- scm-ui/src/repos/components/ChangesetId.js | 25 +++++++++++++++++++++ scm-ui/src/repos/components/ChangesetRow.js | 14 +++--------- 2 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 scm-ui/src/repos/components/ChangesetId.js diff --git a/scm-ui/src/repos/components/ChangesetId.js b/scm-ui/src/repos/components/ChangesetId.js new file mode 100644 index 0000000000..7669cd606e --- /dev/null +++ b/scm-ui/src/repos/components/ChangesetId.js @@ -0,0 +1,25 @@ +//@flow + +import { Link } from "react-router-dom"; +import React from "react"; +import type { Repository, Changeset } from "@scm-manager/ui-types"; + +type Props = { + repository: Repository, + changeset: Changeset +}; + +export default class ChangesetId extends React.Component { + render() { + const { repository, changeset } = this.props; + return ( + + {changeset.id.substr(0, 7)} + + ); + } +} diff --git a/scm-ui/src/repos/components/ChangesetRow.js b/scm-ui/src/repos/components/ChangesetRow.js index a80b8d2b11..40bcfd59ed 100644 --- a/scm-ui/src/repos/components/ChangesetRow.js +++ b/scm-ui/src/repos/components/ChangesetRow.js @@ -4,9 +4,9 @@ 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-manager/ui-components"; -import { Link } from "react-router-dom"; const styles = { pointer: { @@ -30,19 +30,11 @@ type Props = { class ChangesetRow extends React.Component { createLink = (changeset: Changeset) => { const { repository } = this.props; - return ( - - {changeset.id.substr(0, 7)} - - ); + return ; }; render() { - const { changeset, t, classes } = this.props; + const { changeset, classes } = this.props; const changesetLink = this.createLink(changeset); const dateFromNow = ; const authorLine = ( From d8cf660237fb65077268209795e4e9e2e4ca7533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 19 Sep 2018 16:47:21 +0200 Subject: [PATCH 35/81] Move changeset authro renderer to component --- .../src/repos/components/ChangesetAuthor.js | 27 +++++++++++++++++++ scm-ui/src/repos/components/ChangesetRow.js | 15 ++--------- 2 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 scm-ui/src/repos/components/ChangesetAuthor.js diff --git a/scm-ui/src/repos/components/ChangesetAuthor.js b/scm-ui/src/repos/components/ChangesetAuthor.js new file mode 100644 index 0000000000..d2993daed4 --- /dev/null +++ b/scm-ui/src/repos/components/ChangesetAuthor.js @@ -0,0 +1,27 @@ +//@flow + +import React from "react"; +import type { Changeset } from "@scm-manager/ui-types"; + +type Props = { + changeset: Changeset +}; + +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/ChangesetRow.js b/scm-ui/src/repos/components/ChangesetRow.js index 40bcfd59ed..9ec3a7af58 100644 --- a/scm-ui/src/repos/components/ChangesetRow.js +++ b/scm-ui/src/repos/components/ChangesetRow.js @@ -7,6 +7,7 @@ import ChangesetAvatar from "./ChangesetAvatar"; import ChangesetId from "./ChangesetId"; import injectSheet from "react-jss"; import { DateFromNow } from "@scm-manager/ui-components"; +import ChangesetAuthor from "./ChangesetAuthor"; const styles = { pointer: { @@ -37,19 +38,7 @@ class ChangesetRow extends React.Component { const { changeset, classes } = this.props; const changesetLink = this.createLink(changeset); const dateFromNow = ; - const authorLine = ( - <> - {changeset.author.name}{" "} - - < - {changeset.author.mail} - > - - - ); + const authorLine = ; return (
From f1d2a41c0906154db579c31f99887fefec505d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 19 Sep 2018 16:49:53 +0200 Subject: [PATCH 36/81] Fix translation --- scm-ui/public/locales/en/changesets.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-ui/public/locales/en/changesets.json b/scm-ui/public/locales/en/changesets.json index 8c804d8f62..b3e996e094 100644 --- a/scm-ui/public/locales/en/changesets.json +++ b/scm-ui/public/locales/en/changesets.json @@ -4,7 +4,7 @@ "description": "Description", "contact": "Contact", "date": "Date", - "summary": "Changeset {{id}} committed at {{time}}" + "summary": "Changeset {{id}} committed {{time}}" }, "author": { "name": "Author", From ba8ce698e12f728a171d4eb149e4fa50d2cb5cb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 19 Sep 2018 17:17:00 +0200 Subject: [PATCH 37/81] Fix paging for log command --- .../api/v2/resources/BranchRootResource.java | 7 ++-- .../v2/resources/ChangesetRootResource.java | 8 +++-- .../v2/resources/FileHistoryRootResource.java | 7 ++-- .../v2/resources/PagedLogCommandBuilder.java | 30 ++++++++++++++++ .../resources/ChangesetRootResourceTest.java | 34 ++++++++++++++++--- 5 files changed, 73 insertions(+), 13 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/PagedLogCommandBuilder.java diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java index 9763f11def..80b8f7ddc7 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java @@ -100,9 +100,10 @@ public class BranchRootResource { try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { Repository repository = repositoryService.getRepository(); RepositoryPermissions.read(repository).check(); - ChangesetPagingResult changesets = repositoryService.getLogCommand() - .setPagingStart(page) - .setPagingLimit(pageSize) + ChangesetPagingResult changesets = new PagedLogCommandBuilder(repositoryService) + .page(page) + .pageSize(pageSize) + .create() .setBranch(branchName) .getChangesets(); if (changesets != null && changesets.getChangesets() != null) { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java index 717b8d7198..97a23684a5 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java @@ -12,6 +12,7 @@ import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryNotFoundException; import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.RevisionNotFoundException; +import sonia.scm.repository.api.LogCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.web.VndMediaType; @@ -59,9 +60,10 @@ public class ChangesetRootResource { try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { Repository repository = repositoryService.getRepository(); RepositoryPermissions.read(repository).check(); - ChangesetPagingResult changesets = repositoryService.getLogCommand() - .setPagingStart(page) - .setPagingLimit(pageSize) + ChangesetPagingResult changesets = new PagedLogCommandBuilder(repositoryService) + .page(page) + .pageSize(pageSize) + .create() .getChangesets(); if (changesets != null && changesets.getChangesets() != null) { PageResult pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal()); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java index 118cc4167a..e38a6f699a 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java @@ -73,9 +73,10 @@ public class FileHistoryRootResource { try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { log.info("Get changesets of the file {} and revision {}", path, revision); Repository repository = repositoryService.getRepository(); - ChangesetPagingResult changesets = repositoryService.getLogCommand() - .setPagingStart(page) - .setPagingLimit(pageSize) + ChangesetPagingResult changesets = new PagedLogCommandBuilder(repositoryService) + .page(page) + .pageSize(pageSize) + .create() .setPath(path) .setStartChangeset(revision) .getChangesets(); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PagedLogCommandBuilder.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PagedLogCommandBuilder.java new file mode 100644 index 0000000000..c2af89e152 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PagedLogCommandBuilder.java @@ -0,0 +1,30 @@ +package sonia.scm.api.v2.resources; + +import sonia.scm.repository.api.LogCommandBuilder; +import sonia.scm.repository.api.RepositoryService; + +class PagedLogCommandBuilder { + private final RepositoryService repositoryService; + private int page; + private int pageSize ; + + PagedLogCommandBuilder(RepositoryService repositoryService) { + this.repositoryService = repositoryService; + } + + PagedLogCommandBuilder page(int page) { + this.page = page; + return this; + } + + PagedLogCommandBuilder pageSize(int pageSize) { + this.pageSize = pageSize; + return this; + } + + LogCommandBuilder create() { + return repositoryService.getLogCommand() + .setPagingStart(page * pageSize) + .setPagingLimit(pageSize); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java index b80b62167b..4e1a0f90f1 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java @@ -109,8 +109,8 @@ public class ChangesetRootResourceTest extends RepositoryTestBase { List changesetList = Lists.newArrayList(new Changeset(id, Date.from(creationDate).getTime(), new Person(authorName, authorEmail), commit)); when(changesetPagingResult.getChangesets()).thenReturn(changesetList); when(changesetPagingResult.getTotal()).thenReturn(1); - when(logCommandBuilder.setPagingStart(anyInt())).thenReturn(logCommandBuilder); - when(logCommandBuilder.setPagingLimit(anyInt())).thenReturn(logCommandBuilder); + when(logCommandBuilder.setPagingStart(0)).thenReturn(logCommandBuilder); + when(logCommandBuilder.setPagingLimit(10)).thenReturn(logCommandBuilder); when(logCommandBuilder.setBranch(anyString())).thenReturn(logCommandBuilder); when(logCommandBuilder.getChangesets()).thenReturn(changesetPagingResult); MockHttpRequest request = MockHttpRequest @@ -126,6 +126,34 @@ public class ChangesetRootResourceTest extends RepositoryTestBase { assertTrue(response.getContentAsString().contains(String.format("\"description\":\"%s\"", commit))); } + @Test + public void shouldGetSinglePageOfChangeSets() throws Exception { + String id = "revision_123"; + Instant creationDate = Instant.now(); + String authorName = "name"; + String authorEmail = "em@i.l"; + String commit = "my branch commit"; + ChangesetPagingResult changesetPagingResult = mock(ChangesetPagingResult.class); + List changesetList = Lists.newArrayList(new Changeset(id, Date.from(creationDate).getTime(), new Person(authorName, authorEmail), commit)); + when(changesetPagingResult.getChangesets()).thenReturn(changesetList); + when(changesetPagingResult.getTotal()).thenReturn(1); + when(logCommandBuilder.setPagingStart(20)).thenReturn(logCommandBuilder); + when(logCommandBuilder.setPagingLimit(10)).thenReturn(logCommandBuilder); + when(logCommandBuilder.setBranch(anyString())).thenReturn(logCommandBuilder); + when(logCommandBuilder.getChangesets()).thenReturn(changesetPagingResult); + MockHttpRequest request = MockHttpRequest + .get(CHANGESET_URL + "?page=2") + .accept(VndMediaType.CHANGESET_COLLECTION); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(200, response.getStatus()); + log.info("Response :{}", response.getContentAsString()); + assertTrue(response.getContentAsString().contains(String.format("\"id\":\"%s\"", id))); + assertTrue(response.getContentAsString().contains(String.format("\"name\":\"%s\"", authorName))); + assertTrue(response.getContentAsString().contains(String.format("\"mail\":\"%s\"", authorEmail))); + assertTrue(response.getContentAsString().contains(String.format("\"description\":\"%s\"", commit))); + } + @Test public void shouldGetChangeSet() throws Exception { String id = "revision_123"; @@ -137,8 +165,6 @@ public class ChangesetRootResourceTest extends RepositoryTestBase { List changesetList = Lists.newArrayList(new Changeset(id, Date.from(creationDate).getTime(), new Person(authorName, authorEmail), commit)); when(changesetPagingResult.getChangesets()).thenReturn(changesetList); when(changesetPagingResult.getTotal()).thenReturn(1); - when(logCommandBuilder.setPagingStart(anyInt())).thenReturn(logCommandBuilder); - when(logCommandBuilder.setPagingLimit(anyInt())).thenReturn(logCommandBuilder); when(logCommandBuilder.setEndChangeset(anyString())).thenReturn(logCommandBuilder); when(logCommandBuilder.setStartChangeset(anyString())).thenReturn(logCommandBuilder); when(logCommandBuilder.getChangesets()).thenReturn(changesetPagingResult); From 57a4e42da6220848286278342ee6787a20bcad73 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Wed, 19 Sep 2018 17:18:24 +0200 Subject: [PATCH 38/81] 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 39/81] 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 40/81] 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 41/81] 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 42/81] 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 43/81] 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 44/81] 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 45/81] 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 46/81] 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 47/81] 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 48/81] 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 49/81] 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 50/81] 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 51/81] 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 52/81] 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 53/81] 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 54/81] 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 55/81] 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 56/81] 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 57/81] 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 58/81] 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 59/81] 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 (
    From 53fe671a2771ab8d190bac46a3bd7c18acdf26bd Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Fri, 12 Oct 2018 16:07:24 +0200 Subject: [PATCH 60/81] Update RouteDelegate only conditionally --- scm-ui/src/repos/containers/BranchChooser.js | 30 ++++++++------ scm-ui/src/repos/containers/BranchRoot.js | 42 +++++++++++++++----- scm-ui/src/repos/containers/Changesets.js | 13 ++++-- 3 files changed, 58 insertions(+), 27 deletions(-) diff --git a/scm-ui/src/repos/containers/BranchChooser.js b/scm-ui/src/repos/containers/BranchChooser.js index e5ee6129e5..4d37c937b3 100644 --- a/scm-ui/src/repos/containers/BranchChooser.js +++ b/scm-ui/src/repos/containers/BranchChooser.js @@ -48,7 +48,7 @@ class BranchChooser extends React.Component { render() { console.log("Branch chooser render"); - const { loading, error, t } = this.props; + const { loading, error, t, repository } = this.props; if (error) { return ( @@ -60,6 +60,10 @@ class BranchChooser extends React.Component { ); } + if (!repository) { + return null; + } + if (loading) { return ; } @@ -85,8 +89,7 @@ class BranchChooser extends React.Component { renderBranchChooser() { const { label, match, branches } = this.props; - const selectedBranchName = ""; - // const selectedBranchName = match.params.branch; + const selectedBranchName = match.params.branch; if (!branches || branches.length === 0) { return null; @@ -127,12 +130,11 @@ const mapStateToProps = (state: any, ownProps: Props) => { const { repository, match } = ownProps; const loading = isFetchBranchesPending(state, repository); const error = getFetchBranchesFailure(state, repository); - // const selectedBranch = getBranch( - // state, - // repository, - // decodeURIComponent(match.params.branch) - // ); - const selectedBranch = ""; + const selectedBranch = getBranch( + state, + repository, + decodeURIComponent(match.params.branch) + ); const branches = getBranches(state, repository); return { // loading, @@ -142,7 +144,9 @@ const mapStateToProps = (state: any, ownProps: Props) => { }; }; -export default connect( - mapStateToProps, - mapDispatchToProps -)(translate("repos")(BranchChooser)); +export default withRouter( + connect( + mapStateToProps, + mapDispatchToProps + )(translate("repos")(BranchChooser)) +); diff --git a/scm-ui/src/repos/containers/BranchRoot.js b/scm-ui/src/repos/containers/BranchRoot.js index 4929715860..4d98dbde29 100644 --- a/scm-ui/src/repos/containers/BranchRoot.js +++ b/scm-ui/src/repos/containers/BranchRoot.js @@ -1,7 +1,7 @@ // @flow import React from "react"; -import type { Repository } from "@scm-manager/ui-types"; +import type { Repository, Branch } from "@scm-manager/ui-types"; import BranchChooser from "./BranchChooser"; import { Route, withRouter } from "react-router-dom"; import Changesets from "./Changesets"; @@ -36,28 +36,48 @@ class BranchRoot extends React.Component { }; render() { + const { repository } = this.props; const url = this.matchedUrl(); - + if (!repository) { + return null; + } return ( - - {/**/} + ); } } -function RouteDelegate(props) { - return ( - } - /> - ); +type RDProps = { + repository: Repository, + branch: Branch, + url: string +}; + +class RouteDelegate extends React.Component { + shouldComponentUpdate(nextProps: RDProps, nextState: any) { + return ( + nextProps.repository !== this.props.repository || + nextProps.branch !== this.props.branch || + nextProps.url !== this.props.url + ); + } + + render() { + const { url, repository, branch } = this.props; + return ( + } + /> + ); + } } export default withRouter(BranchRoot); diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index e106217232..4d26ea04a1 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -36,7 +36,7 @@ type Props = { type State = {}; -class ChangesetContainer extends React.Component { +class Changesets extends React.Component { componentDidMount() { const { fetchChangesetsByBranch, @@ -45,6 +45,9 @@ class ChangesetContainer extends React.Component { branch, match } = this.props; + + console.log("branch"); + console.log(branch); const { page } = match.params; if (!page) { fetchChangesetsByBranch(repository, branch); @@ -54,7 +57,11 @@ class ChangesetContainer extends React.Component { } render() { - const { changesets, loading, error, t } = this.props; + const { repository, branch, changesets, loading, error, t } = this.props; + + if (!repository || !branch) { + return null; + } if (error) { return ( @@ -121,5 +128,5 @@ export default withRouter( connect( mapStateToProps, mapDispatchToProps - )(translate("repos")(ChangesetContainer)) + )(translate("repos")(Changesets)) ); From e6fb2a8de7fadf3b9d15b862cbb0392b4dddffe9 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Tue, 16 Oct 2018 17:04:28 +0200 Subject: [PATCH 61/81] Introduced dumb BranchSelector; moved fetching of branches to BranchRoot --- scm-ui/src/repos/components/DropDown.js | 9 +- scm-ui/src/repos/containers/BranchChooser.js | 84 ++++++---- scm-ui/src/repos/containers/BranchRoot.js | 157 ++++++++++++------ scm-ui/src/repos/containers/BranchSelector.js | 46 +++++ scm-ui/src/repos/containers/Changesets.js | 32 +++- scm-ui/src/repos/containers/RepositoryRoot.js | 50 +++--- scm-ui/src/repos/modules/changesets.js | 22 ++- 7 files changed, 275 insertions(+), 125 deletions(-) create mode 100644 scm-ui/src/repos/containers/BranchSelector.js diff --git a/scm-ui/src/repos/components/DropDown.js b/scm-ui/src/repos/components/DropDown.js index bd44e30f59..d68ec9ce35 100644 --- a/scm-ui/src/repos/components/DropDown.js +++ b/scm-ui/src/repos/components/DropDown.js @@ -5,7 +5,7 @@ import React from "react"; type Props = { options: string[], optionSelected: string => void, - preselectedOption: string + preselectedOption?: string }; class DropDown extends React.Component { @@ -13,7 +13,10 @@ class DropDown extends React.Component { const { options, preselectedOption } = this.props; return (
    -
    ); } - branchSelected = (branch: string) => { + branchSelected = (branch: string, changed: boolean) => { for (let b of this.props.branches) { if (b.name === branch) { - this.setState({ selectedBranch: b }); - this.props.branchSelected(b.name); + this.updateBranch(branch, b, changed); break; } } }; + + updateBranch = (branchName: string, branch: Branch, changed: boolean) => { + this.setState( + prevState => { + if ( + !prevState.selectedBranch || + branchName !== prevState.selectedBranch.name + ) { + return { selectedBranch: branch }; + } + }, + () => { + if (changed) { + this.props.onChange(branch.name); + } + } + ); + }; } const mapDispatchToProps = dispatch => { @@ -127,26 +148,19 @@ const mapDispatchToProps = dispatch => { }; const mapStateToProps = (state: any, ownProps: Props) => { - const { repository, match } = ownProps; + const { repository } = ownProps; const loading = isFetchBranchesPending(state, repository); const error = getFetchBranchesFailure(state, repository); - const selectedBranch = getBranch( - state, - repository, - decodeURIComponent(match.params.branch) - ); + const branches = getBranches(state, repository); return { - // loading, - selectedBranch, - // error, + loading, + error, branches }; }; -export default withRouter( - connect( - mapStateToProps, - mapDispatchToProps - )(translate("repos")(BranchChooser)) -); +export default connect( + mapStateToProps, + mapDispatchToProps +)(translate("repos")(BranchChooser)); diff --git a/scm-ui/src/repos/containers/BranchRoot.js b/scm-ui/src/repos/containers/BranchRoot.js index 4d98dbde29..b7ac0a45ca 100644 --- a/scm-ui/src/repos/containers/BranchRoot.js +++ b/scm-ui/src/repos/containers/BranchRoot.js @@ -2,82 +2,145 @@ import React from "react"; import type { Repository, Branch } from "@scm-manager/ui-types"; -import BranchChooser from "./BranchChooser"; -import { Route, withRouter } from "react-router-dom"; +import { Route, Switch, withRouter } from "react-router-dom"; import Changesets from "./Changesets"; +import BranchSelector from "./BranchSelector"; +import { connect } from "react-redux"; +import { ErrorPage, Loading } from "@scm-manager/ui-components"; +import { + fetchBranches, + getBranches, + getFetchBranchesFailure, + isFetchBranchesPending +} from "../modules/branches"; +import { compose } from "redux"; type Props = { repository: Repository, + baseUrl: string, + + // State props + branches: Branch[], + loading: boolean, + + // Dispatch props + fetchBranches: Repository => void, + + // Context props history: History, match: any }; -class BranchRoot extends React.Component { +type State = { + selectedBranch?: Branch +}; + +class BranchRoot extends React.PureComponent { + constructor(props: Props) { + super(props); + this.state = {}; + } + + componentDidMount() { + this.props.fetchBranches(this.props.repository); + } + + componentDidUpdate(prevProps: Props) { + const { branches, match, loading } = this.props; + console.log("BR did update"); + const branchName = decodeURIComponent(match.params.branch); + if (branches) { + if ( + (!loading && prevProps.loading) || + match.url !== prevProps.match.url + ) { + this.setState({ + selectedBranch: branches.find(b => b.name === branchName) + }); + } + } + } + stripEndingSlash = (url: string) => { if (url.endsWith("/")) { - return url.substring(0, url.length - 2); + return url.substring(0, url.length - 1); } return url; }; matchedUrl = () => { - return this.stripEndingSlash(this.props.match.url); + return this.stripEndingSlash(this.props.baseUrl); }; - branchSelected = (branchName: string) => { + branchSelected = (branch: Branch) => { const url = this.matchedUrl(); - if (branchName === "") { - this.props.history.push(`${url}/changesets/`); - } else { - this.props.history.push( - `${url}/${encodeURIComponent(branchName)}/changesets/` - ); - } + this.props.history.push( + `${url}/${encodeURIComponent(branch.name)}/changesets/` + ); }; render() { - const { repository } = this.props; - const url = this.matchedUrl(); - if (!repository) { + const { repository, match, branches, loading } = this.props; + const url = this.stripEndingSlash(match.url); + + if (loading) { + return ; + } + + if (!repository || !branches) { return null; } + + return ( - - - + <> + { + this.branchSelected(b); + }} + /> + ( + + )} + /> + + ); } } -type RDProps = { - repository: Repository, - branch: Branch, - url: string + +const mapDispatchToProps = dispatch => { + return { + fetchBranches: (repo: Repository) => { + dispatch(fetchBranches(repo)); + } + }; }; -class RouteDelegate extends React.Component { - shouldComponentUpdate(nextProps: RDProps, nextState: any) { - return ( - nextProps.repository !== this.props.repository || - nextProps.branch !== this.props.branch || - nextProps.url !== this.props.url - ); - } +const mapStateToProps = (state: any, ownProps: Props) => { + const { repository } = ownProps; + const loading = isFetchBranchesPending(state, repository); + const error = getFetchBranchesFailure(state, repository); - render() { - const { url, repository, branch } = this.props; - return ( - } - /> - ); - } -} + const branches = getBranches(state, repository); + return { + loading, + error, + branches + }; +}; -export default withRouter(BranchRoot); +export default compose( + connect( + mapStateToProps, + mapDispatchToProps + ), + withRouter +)(BranchRoot); diff --git a/scm-ui/src/repos/containers/BranchSelector.js b/scm-ui/src/repos/containers/BranchSelector.js new file mode 100644 index 0000000000..bec66ca143 --- /dev/null +++ b/scm-ui/src/repos/containers/BranchSelector.js @@ -0,0 +1,46 @@ +// @flow + +import React from "react"; +import type { Branch } from "@scm-manager/ui-types"; +import DropDown from "../components/DropDown"; + +type Props = { + branches: Branch[], // TODO: Use generics? + selected?: Branch => void +}; + +type State = { selectedBranch?: Branch }; +class BranchSelector extends React.Component { + constructor(props: Props) { + super(props); + this.state = {}; + } + render() { + const { branches } = this.props; + if (branches) { + return ( + <> + b.name)} + optionSelected={this.branchSelected} + preselectedOption={ + this.state.selectedBranch ? this.state.selectedBranch.name : "" + } + /> + + ); + } + } + + branchSelected = (branchName: string) => { + const { branches, selected } = this.props; + const branch = branches.find(b => b.name === branchName); + + if (branch) { + selected(branch); + this.setState({ selectedBranch: branch }); + } + }; +} + +export default BranchSelector; diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index 4d26ea04a1..6fd6cac825 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -46,9 +46,31 @@ class Changesets extends React.Component { match } = this.props; - console.log("branch"); - console.log(branch); const { page } = match.params; + if (!branch) { + return; + } + if (!page) { + fetchChangesetsByBranch(repository, branch); + } else { + fetchChangesetsByBranchAndPage(repository, branch, page); + } + } + + componentDidUpdate(prevProps: Props) { + const { + match, + repository, + branch, + fetchChangesetsByBranch, + fetchChangesetsByBranchAndPage + } = this.props; + const { page } = match.params; + + if (branch === prevProps.branch) { + return; + } + if (!page) { fetchChangesetsByBranch(repository, branch); } else { @@ -57,11 +79,7 @@ class Changesets extends React.Component { } render() { - const { repository, branch, changesets, loading, error, t } = this.props; - - if (!repository || !branch) { - return null; - } + const { changesets, loading, error, t } = this.props; if (error) { return ( diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 7acf3188b0..f248bd07eb 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -8,7 +8,7 @@ import { isFetchRepoPending } from "../modules/repos"; import { connect } from "react-redux"; -import { Route } from "react-router-dom"; +import { Route, Switch } from "react-router-dom"; import type { Repository } from "@scm-manager/ui-types"; import { ErrorPage, @@ -98,36 +98,32 @@ 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) + // todo: default branch return (
    - } - /> - } - /> - {/* (*/} - {/**/} - {/**/} - {/**/} - {/*)}*/} - {/*/>*/} - } - /> + + } + /> + } + /> + + ( + + )} + /> +
    diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index 191de7b37d..925f37af2e 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, Branch, 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, + 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}`; From f215fc4117660bebf70ff82549da8f60252da850 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Tue, 16 Oct 2018 20:19:22 +0200 Subject: [PATCH 62/81] Debugging attempts --- scm-ui/src/containers/Main.js | 5 +- scm-ui/src/repos/containers/BranchRoot.js | 20 +++--- scm-ui/src/repos/containers/Changesets.js | 68 ++++++++++--------- scm-ui/src/repos/containers/RepositoryRoot.js | 29 ++------ 4 files changed, 53 insertions(+), 69 deletions(-) diff --git a/scm-ui/src/containers/Main.js b/scm-ui/src/containers/Main.js index 770985360c..0a5f99da43 100644 --- a/scm-ui/src/containers/Main.js +++ b/scm-ui/src/containers/Main.js @@ -1,15 +1,14 @@ //@flow import React from "react"; -import { Route, Redirect, withRouter } from "react-router"; +import {Redirect, Route, Switch, withRouter} from "react-router-dom"; import Overview from "../repos/containers/Overview"; import Users from "../users/containers/Users"; import Login from "../containers/Login"; import Logout from "../containers/Logout"; -import { Switch } from "react-router-dom"; -import { ProtectedRoute } from "@scm-manager/ui-components"; +import {ProtectedRoute} from "@scm-manager/ui-components"; import AddUser from "../users/containers/AddUser"; import SingleUser from "../users/containers/SingleUser"; import RepositoryRoot from "../repos/containers/RepositoryRoot"; diff --git a/scm-ui/src/repos/containers/BranchRoot.js b/scm-ui/src/repos/containers/BranchRoot.js index b7ac0a45ca..8b1965044a 100644 --- a/scm-ui/src/repos/containers/BranchRoot.js +++ b/scm-ui/src/repos/containers/BranchRoot.js @@ -1,12 +1,12 @@ // @flow import React from "react"; -import type { Repository, Branch } from "@scm-manager/ui-types"; -import { Route, Switch, withRouter } from "react-router-dom"; +import type { Branch, Repository } from "@scm-manager/ui-types"; +import { Route, withRouter } from "react-router-dom"; import Changesets from "./Changesets"; import BranchSelector from "./BranchSelector"; import { connect } from "react-redux"; -import { ErrorPage, Loading } from "@scm-manager/ui-components"; +import { Loading } from "@scm-manager/ui-components"; import { fetchBranches, getBranches, @@ -35,13 +35,14 @@ type State = { selectedBranch?: Branch }; -class BranchRoot extends React.PureComponent { +class BranchRoot extends React.Component { constructor(props: Props) { super(props); this.state = {}; } componentDidMount() { + console.log("BR did mount"); this.props.fetchBranches(this.props.repository); } @@ -75,23 +76,22 @@ class BranchRoot extends React.PureComponent { branchSelected = (branch: Branch) => { const url = this.matchedUrl(); this.props.history.push( - `${url}/${encodeURIComponent(branch.name)}/changesets/` + `${url}/${encodeURIComponent(branch.name)}/changesets` ); }; render() { + console.log("BR render"); const { repository, match, branches, loading } = this.props; const url = this.stripEndingSlash(match.url); if (loading) { return ; } - if (!repository || !branches) { return null; } - return ( <> { /> )} /> - ); } } - const mapDispatchToProps = dispatch => { return { fetchBranches: (repo: Repository) => { @@ -138,9 +136,9 @@ const mapStateToProps = (state: any, ownProps: Props) => { }; export default compose( + withRouter, connect( mapStateToProps, mapDispatchToProps - ), - withRouter + ) )(BranchRoot); diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index 6fd6cac825..c88b2d40a1 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -1,13 +1,8 @@ // @flow import React from "react"; -import { withRouter } from "react-router-dom"; -import type { - Branch, - Changeset, - PagedCollection, - Repository -} from "@scm-manager/ui-types"; +import {withRouter} from "react-router-dom"; +import type {Branch, Changeset, PagedCollection, Repository} from "@scm-manager/ui-types"; import { fetchChangesetsByBranch, fetchChangesetsByBranchAndPage, @@ -16,21 +11,27 @@ import { isFetchChangesetsPending, selectListAsCollection } from "../modules/changesets"; -import { connect } from "react-redux"; +import {connect} from "react-redux"; import ChangesetList from "../components/changesets/ChangesetList"; -import { ErrorPage, LinkPaginator, Loading } from "@scm-manager/ui-components"; -import { translate } from "react-i18next"; +import {ErrorPage, LinkPaginator, Loading} from "@scm-manager/ui-components"; +import {translate} from "react-i18next"; type Props = { - fetchChangesetsByBranch: (Repository, Branch) => void, - fetchChangesetsByBranchAndPage: (Repository, Branch, number) => void, repository: Repository, //TODO: Do we really need/want this here? branch: Branch, + + // State props changesets: Changeset[], loading: boolean, - match: any, list: PagedCollection, error: Error, + + // Dispatch props + fetchChangesetsByBranch: (Repository, Branch) => void, + fetchChangesetsByBranchAndPage: (Repository, Branch, number) => void, + + // Context Props + match: any, t: string => string }; @@ -38,6 +39,7 @@ type State = {}; class Changesets extends React.Component { componentDidMount() { + console.log("CDM"); const { fetchChangesetsByBranch, fetchChangesetsByBranchAndPage, @@ -57,26 +59,26 @@ class Changesets extends React.Component { } } - componentDidUpdate(prevProps: Props) { - const { - match, - repository, - branch, - fetchChangesetsByBranch, - fetchChangesetsByBranchAndPage - } = this.props; - const { page } = match.params; - - if (branch === prevProps.branch) { - return; - } - - if (!page) { - fetchChangesetsByBranch(repository, branch); - } else { - fetchChangesetsByBranchAndPage(repository, branch, page); - } - } + // componentDidUpdate(prevProps: Props) { + // const { + // match, + // repository, + // branch, + // fetchChangesetsByBranch, + // fetchChangesetsByBranchAndPage + // } = this.props; + // const { page } = match.params; + // + // if (branch === prevProps.branch) { + // return; + // } + // + // if (!page) { + // fetchChangesetsByBranch(repository, branch); + // } else { + // fetchChangesetsByBranchAndPage(repository, branch, page); + // } + // } render() { const { changesets, loading, error, t } = this.props; diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index f248bd07eb..d94d2b855f 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -1,32 +1,17 @@ //@flow import React from "react"; -import { - deleteRepo, - fetchRepo, - getFetchRepoFailure, - getRepository, - isFetchRepoPending -} from "../modules/repos"; -import { connect } from "react-redux"; -import { Route, Switch } from "react-router-dom"; -import type { Repository } from "@scm-manager/ui-types"; -import { - ErrorPage, - Loading, - Navigation, - NavLink, - Page, - 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, Switch} from "react-router-dom"; +import type {Repository} from "@scm-manager/ui-types"; +import {ErrorPage, Loading, Navigation, NavLink, Page, Section} from "@scm-manager/ui-components"; +import {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 BranchChooser from "./BranchChooser"; -import Changesets from "./Changesets"; import BranchRoot from "./BranchRoot"; type Props = { From d330975557d90591aa69dc6701d5a2917ce77b09 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 17 Oct 2018 10:06:26 +0200 Subject: [PATCH 63/81] fix endless loop, by using the same reference for the branch array --- scm-ui/src/repos/containers/BranchRoot.js | 54 ++++++----------- scm-ui/src/repos/containers/BranchSelector.js | 3 + scm-ui/src/repos/containers/Changesets.js | 16 +++-- scm-ui/src/repos/containers/RepositoryRoot.js | 32 ++++++---- scm-ui/src/repos/modules/branches.js | 58 +++++-------------- scm-ui/src/repos/modules/branches.test.js | 46 +++++++-------- 6 files changed, 90 insertions(+), 119 deletions(-) diff --git a/scm-ui/src/repos/containers/BranchRoot.js b/scm-ui/src/repos/containers/BranchRoot.js index 8b1965044a..1a181ce0b8 100644 --- a/scm-ui/src/repos/containers/BranchRoot.js +++ b/scm-ui/src/repos/containers/BranchRoot.js @@ -18,6 +18,7 @@ import { compose } from "redux"; type Props = { repository: Repository, baseUrl: string, + selected: string, // State props branches: Branch[], @@ -31,37 +32,16 @@ type Props = { match: any }; -type State = { - selectedBranch?: Branch -}; - -class BranchRoot extends React.Component { +class BranchRoot extends React.Component { constructor(props: Props) { super(props); this.state = {}; } componentDidMount() { - console.log("BR did mount"); this.props.fetchBranches(this.props.repository); } - componentDidUpdate(prevProps: Props) { - const { branches, match, loading } = this.props; - console.log("BR did update"); - const branchName = decodeURIComponent(match.params.branch); - if (branches) { - if ( - (!loading && prevProps.loading) || - match.url !== prevProps.match.url - ) { - this.setState({ - selectedBranch: branches.find(b => b.name === branchName) - }); - } - } - } - stripEndingSlash = (url: string) => { if (url.endsWith("/")) { return url.substring(0, url.length - 1); @@ -80,9 +60,14 @@ class BranchRoot extends React.Component { ); }; + findSelectedBranch = () => { + const { selected, branches } = this.props; + return branches.find((branch: Branch) => branch.name === selected); + }; + render() { - console.log("BR render"); - const { repository, match, branches, loading } = this.props; + // TODO error??? + const { repository, loading, match, branches } = this.props; const url = this.stripEndingSlash(match.url); if (loading) { @@ -92,6 +77,9 @@ class BranchRoot extends React.Component { return null; } + const branch = this.findSelectedBranch(); + const changesets = ; + return ( <> { this.branchSelected(b); }} /> - ( - - )} - /> + changesets} /> ); } @@ -123,15 +103,17 @@ const mapDispatchToProps = dispatch => { }; const mapStateToProps = (state: any, ownProps: Props) => { - const { repository } = ownProps; + const { repository, match } = ownProps; const loading = isFetchBranchesPending(state, repository); const error = getFetchBranchesFailure(state, repository); - const branches = getBranches(state, repository); + const selected = decodeURIComponent(match.params.branch); + return { loading, error, - branches + branches, + selected }; }; diff --git a/scm-ui/src/repos/containers/BranchSelector.js b/scm-ui/src/repos/containers/BranchSelector.js index bec66ca143..833ed20ad6 100644 --- a/scm-ui/src/repos/containers/BranchSelector.js +++ b/scm-ui/src/repos/containers/BranchSelector.js @@ -10,13 +10,16 @@ type Props = { }; type State = { selectedBranch?: Branch }; + class BranchSelector extends React.Component { constructor(props: Props) { super(props); this.state = {}; } + render() { const { branches } = this.props; + if (branches) { return ( <> diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index c88b2d40a1..8df046e21a 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -1,8 +1,13 @@ // @flow import React from "react"; -import {withRouter} from "react-router-dom"; -import type {Branch, Changeset, PagedCollection, Repository} from "@scm-manager/ui-types"; +import { withRouter } from "react-router-dom"; +import type { + Branch, + Changeset, + PagedCollection, + Repository +} from "@scm-manager/ui-types"; import { fetchChangesetsByBranch, fetchChangesetsByBranchAndPage, @@ -11,10 +16,10 @@ import { isFetchChangesetsPending, selectListAsCollection } from "../modules/changesets"; -import {connect} from "react-redux"; +import { connect } from "react-redux"; import ChangesetList from "../components/changesets/ChangesetList"; -import {ErrorPage, LinkPaginator, Loading} from "@scm-manager/ui-components"; -import {translate} from "react-i18next"; +import { ErrorPage, LinkPaginator, Loading } from "@scm-manager/ui-components"; +import { translate } from "react-i18next"; type Props = { repository: Repository, //TODO: Do we really need/want this here? @@ -39,7 +44,6 @@ type State = {}; class Changesets extends React.Component { componentDidMount() { - console.log("CDM"); const { fetchChangesetsByBranch, fetchChangesetsByBranchAndPage, diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index d94d2b855f..416e0544a4 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -1,16 +1,29 @@ //@flow import React from "react"; -import {deleteRepo, fetchRepo, getFetchRepoFailure, getRepository, isFetchRepoPending} from "../modules/repos"; -import {connect} from "react-redux"; -import {Route, Switch} from "react-router-dom"; -import type {Repository} from "@scm-manager/ui-types"; -import {ErrorPage, Loading, Navigation, NavLink, Page, 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, Switch } from "react-router-dom"; +import type { Repository } from "@scm-manager/ui-types"; +import { + ErrorPage, + Loading, + Navigation, + NavLink, + Page, + Section +} from "@scm-manager/ui-components"; +import { 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 BranchRoot from "./BranchRoot"; @@ -98,9 +111,8 @@ class RepositoryRoot extends React.Component { path={`${url}/edit`} component={() => } /> - ( { diff --git a/scm-ui/src/repos/modules/branches.js b/scm-ui/src/repos/modules/branches.js index 2a4580e194..3148857f51 100644 --- a/scm-ui/src/repos/modules/branches.js +++ b/scm-ui/src/repos/modules/branches.js @@ -66,79 +66,51 @@ export function fetchBranchesFailure(repository: Repository, error: Error) { // Reducers +type State = { [string]: Branch[] }; + export default function reducer( - state: Object = {}, + state: State = {}, action: Action = { type: "UNKNOWN" } -): Object { +): State { switch (action.type) { case FETCH_BRANCHES_SUCCESS: const key = createKey(action.payload.repository); - let oldBranchesByNames = { [key]: {} }; - if (state[key] !== undefined) { - oldBranchesByNames[key] = state[key]; - } return { - [key]: { - byNames: extractBranchesByNames( - action.payload.data, - oldBranchesByNames[key].byNames - ) - } + ...state, + [key]: extractBranchesFromPayload(action.payload.data) }; default: return state; } } -function extractBranchesByNames(data: any, oldBranchesByNames: any): ?Object { - if (!data._embedded || !data._embedded.branches) { - return {}; +function extractBranchesFromPayload(payload: any) { + if (payload._embedded && payload._embedded.branches) { + return payload._embedded.branches; } - const branches = data._embedded.branches; - const branchesByNames = {}; - - for (let branch of branches) { - branchesByNames[branch.name] = branch; - } - - for (let name in oldBranchesByNames) { - branchesByNames[name] = oldBranchesByNames[name]; - } - return branchesByNames; + return []; } // Selectors -export function getBranchNames( - state: Object, - repository: Repository -): ?Array { - const key = createKey(repository); - if (!state.branches || !state.branches[key] || !state.branches[key].byNames) { - return []; - } - return Object.keys(state.branches[key].byNames); -} +const empty = []; export function getBranches(state: Object, repository: Repository) { const key = createKey(repository); if (state.branches[key]) { - if (state.branches[key].byNames) { - return Object.values(state.branches[key].byNames); - } + return state.branches[key]; } + return empty; } export function getBranch( - state: Object, + state: State, repository: Repository, name: string ): ?Branch { const key = createKey(repository); if (state.branches[key]) { - if (state.branches[key].byNames[name]) { - return state.branches[key].byNames[name]; - } + return state.branches[key].find((b: Branch) => b.name === name); } return null; } diff --git a/scm-ui/src/repos/modules/branches.test.js b/scm-ui/src/repos/modules/branches.test.js index c958f105ef..40abf951cf 100644 --- a/scm-ui/src/repos/modules/branches.test.js +++ b/scm-ui/src/repos/modules/branches.test.js @@ -109,23 +109,20 @@ describe("branches", () => { 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); + expect(newState[key]).toContain(branch1); + expect(newState[key]).toContain(branch2); }); it("should not delete existing branches from state", () => { const oldState = { - "foo/bar": { - byNames: { - branch3: branch3 - } - } + "hitchhiker/heartOfGold": [branch3] }; const newState = reducer(oldState, action); - expect(newState[key].byNames["branch1"]).toEqual(branch1); - expect(newState[key].byNames["branch2"]).toEqual(branch2); - expect(newState[key].byNames["branch3"]).toEqual(branch3); + expect(newState[key]).toContain(branch1); + expect(newState[key]).toContain(branch2); + + expect(newState["hitchhiker/heartOfGold"]).toContain(branch3); }); }); @@ -134,12 +131,7 @@ describe("branches", () => { const state = { branches: { - [key]: { - byNames: { - branch1, - branch2 - } - } + [key]: [branch1, branch2] } }; @@ -153,13 +145,6 @@ describe("branches", () => { expect(isFetchBranchesPending(state, repository)).toBeTruthy(); }); - it("should return branches names", () => { - const names = getBranchNames(state, repository); - expect(names.length).toEqual(2); - expect(names).toContain("branch1"); - expect(names).toContain("branch2"); - }); - it("should return branches", () => { const branches = getBranches(state, repository); expect(branches.length).toEqual(2); @@ -167,15 +152,28 @@ describe("branches", () => { expect(branches).toContain(branch2); }); + it("should return always the same reference for branches", () => { + const one = getBranches(state, repository); + const two = getBranches(state, repository); + expect(one).toBe(two); + }); + it("should return single branch by name", () => { const branch = getBranch(state, repository, "branch1"); expect(branch).toEqual(branch1); }); + it("should return same reference for single branch by name", () => { + const one = getBranch(state, repository, "branch1"); + const two = getBranch(state, repository, "branch1"); + expect(one).toBe(two); + }); + it("should return undefined if branch does not exist", () => { const branch = getBranch(state, repository, "branch42"); - expect(branch).toBeFalsy(); + expect(branch).toBeUndefined(); }); + it("should return error if fetching branches failed", () => { const state = { failure: { From 4cbbc16b6ae65017e6dfad3af383fee729ca9954 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 17 Oct 2018 10:38:46 +0200 Subject: [PATCH 64/81] simplify changesets module --- scm-ui/src/repos/containers/Changesets.js | 74 ++++------------ scm-ui/src/repos/modules/changesets.js | 98 +++++---------------- scm-ui/src/repos/modules/changesets.test.js | 69 ++++++--------- 3 files changed, 64 insertions(+), 177 deletions(-) diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index 8df046e21a..21417b6715 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -9,13 +9,13 @@ import type { Repository } from "@scm-manager/ui-types"; import { - fetchChangesetsByBranch, - fetchChangesetsByBranchAndPage, + fetchChangesets, getChangesets, getFetchChangesetsFailure, isFetchChangesetsPending, selectListAsCollection } from "../modules/changesets"; + import { connect } from "react-redux"; import ChangesetList from "../components/changesets/ChangesetList"; import { ErrorPage, LinkPaginator, Loading } from "@scm-manager/ui-components"; @@ -24,66 +24,28 @@ import { translate } from "react-i18next"; type Props = { repository: Repository, //TODO: Do we really need/want this here? branch: Branch, + page: number, // State props changesets: Changeset[], - loading: boolean, list: PagedCollection, + loading: boolean, error: Error, // Dispatch props - fetchChangesetsByBranch: (Repository, Branch) => void, - fetchChangesetsByBranchAndPage: (Repository, Branch, number) => void, + fetchChangesets: (Repository, Branch, number) => void, // Context Props - match: any, t: string => string }; -type State = {}; - -class Changesets extends React.Component { +class Changesets extends React.Component { componentDidMount() { - const { - fetchChangesetsByBranch, - fetchChangesetsByBranchAndPage, - repository, - branch, - match - } = this.props; + const { fetchChangesets, repository, branch, page } = this.props; - const { page } = match.params; - if (!branch) { - return; - } - if (!page) { - fetchChangesetsByBranch(repository, branch); - } else { - fetchChangesetsByBranchAndPage(repository, branch, page); - } + fetchChangesets(repository, branch, page); } - // componentDidUpdate(prevProps: Props) { - // const { - // match, - // repository, - // branch, - // fetchChangesetsByBranch, - // fetchChangesetsByBranchAndPage - // } = this.props; - // const { page } = match.params; - // - // if (branch === prevProps.branch) { - // return; - // } - // - // if (!page) { - // fetchChangesetsByBranch(repository, branch); - // } else { - // fetchChangesetsByBranchAndPage(repository, branch, page); - // } - // } - render() { const { changesets, loading, error, t } = this.props; @@ -127,27 +89,25 @@ class Changesets extends React.Component { const mapDispatchToProps = dispatch => { return { - fetchChangesetsByBranch: (repo: Repository, branch: Branch) => { - dispatch(fetchChangesetsByBranch(repo, branch)); - }, - fetchChangesetsByBranchAndPage: ( - repo: Repository, - branch: Branch, - page: number - ) => { - dispatch(fetchChangesetsByBranchAndPage(repo, branch, page)); + fetchChangesets: (repo: Repository, branch: Branch, page: number) => { + dispatch(fetchChangesets(repo, branch, page)); } }; }; const mapStateToProps = (state: any, ownProps: Props) => { - const { repository, branch } = ownProps; + const { repository, branch, match } = ownProps; const changesets = getChangesets(state, repository, branch); const loading = isFetchChangesetsPending(state, repository, branch); const error = getFetchChangesetsFailure(state, repository, branch); const list = selectListAsCollection(state, repository, branch); - return { changesets, list, loading, error }; + + // TODO + const page = parseInt(match.params.page || "1"); + + return { changesets, list, page, loading, error }; }; + export default withRouter( connect( mapStateToProps, diff --git a/scm-ui/src/repos/modules/changesets.js b/scm-ui/src/repos/modules/changesets.js index 925f37af2e..53df4a1099 100644 --- a/scm-ui/src/repos/modules/changesets.js +++ b/scm-ui/src/repos/modules/changesets.js @@ -22,33 +22,34 @@ export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; export const FETCH_CHANGESETS_SUCCESS = `${FETCH_CHANGESETS}_${SUCCESS_SUFFIX}`; export const FETCH_CHANGESETS_FAILURE = `${FETCH_CHANGESETS}_${FAILURE_SUFFIX}`; -const REPO_URL = "repositories"; //TODO: Content type // actions -export function fetchChangesetsByLink( +export function fetchChangesets( repository: Repository, - link: string, - branch?: Branch + branch?: Branch, + page: number ) { + const link = createChangesetsLink(repository, branch, page); + return function(dispatch: any) { dispatch(fetchChangesetsPending(repository, branch)); return apiClient .get(link) .then(response => response.json()) .then(data => { - dispatch(fetchChangesetsSuccess(data, repository, branch)); + dispatch(fetchChangesetsSuccess(repository, branch, data)); }) .catch(cause => { - dispatch(fetchChangesetsFailure(repository, cause, branch)); + dispatch(fetchChangesetsFailure(repository, branch, cause)); }); }; } -export function fetchChangesetsWithOptions( +function createChangesetsLink( repository: Repository, branch?: Branch, - suffix?: string + page: number ) { let link = repository._links.changesets.href; @@ -56,45 +57,10 @@ export function fetchChangesetsWithOptions( link = branch._links.history.href; } - if (suffix) { - link = link + `${suffix}`; + if (page) { + link = link + `?page=${page - 1}`; } - - return function(dispatch: any) { - dispatch(fetchChangesetsPending(repository, branch)); - return apiClient - .get(link) - .then(response => response.json()) - .then(data => { - dispatch(fetchChangesetsSuccess(data, repository, branch)); - }) - .catch(cause => { - dispatch(fetchChangesetsFailure(repository, cause, branch)); - }); - }; -} - -export function fetchChangesets(repository: Repository) { - return fetchChangesetsWithOptions(repository); -} - -export function fetchChangesetsByPage(repository: Repository, page: number) { - return fetchChangesetsWithOptions(repository, undefined, `?page=${page - 1}`); -} - -export function fetchChangesetsByBranchAndPage( - repository: Repository, - branch: Branch, - page: number -) { - return fetchChangesetsWithOptions(repository, branch, `?page=${page - 1}`); -} - -export function fetchChangesetsByBranch( - repository: Repository, - branch: Branch -) { - return fetchChangesetsWithOptions(repository, branch); + return link; } export function fetchChangesetsPending( @@ -105,15 +71,14 @@ export function fetchChangesetsPending( return { type: FETCH_CHANGESETS_PENDING, - payload: { repository, branch }, itemId }; } export function fetchChangesetsSuccess( - changesets: any, repository: Repository, - branch?: Branch + branch?: Branch, + changesets: any ): Action { return { type: FETCH_CHANGESETS_SUCCESS, @@ -124,8 +89,8 @@ export function fetchChangesetsSuccess( function fetchChangesetsFailure( repository: Repository, - error: Error, - branch?: Branch + branch?: Branch, + error: Error ): Action { return { type: FETCH_CHANGESETS_FAILURE, @@ -141,14 +106,14 @@ function fetchChangesetsFailure( function createItemId(repository: Repository, branch?: Branch): string { const { namespace, name } = repository; let itemId = namespace + "/" + name; - if (branch && branch !== "") { + if (branch) { itemId = itemId + "/" + branch.name; } return itemId; } // reducer -function byKeyReducer( +export default function reducer( state: any = {}, action: Action = { type: "UNKNOWN" } ): Object { @@ -186,10 +151,6 @@ function byKeyReducer( } } -export default combineReducers({ - byKey: byKeyReducer -}); - function extractChangesetsByIds(changesets: any) { const changesetsByIds = {}; @@ -207,10 +168,10 @@ export function getChangesets( branch?: Branch ) { const key = createItemId(repository, branch); - if (!state.changesets.byKey[key]) { + if (!state.changesets[key]) { return null; } - return Object.values(state.changesets.byKey[key].byId); + return Object.values(state.changesets[key].byId); } export function isFetchChangesetsPending( @@ -231,8 +192,8 @@ export function getFetchChangesetsFailure( 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; + if (state.changesets[itemId] && state.changesets[itemId].list) { + return state.changesets[itemId].list; } return {}; }; @@ -256,18 +217,3 @@ export const selectListAsCollection = ( ): PagedCollection => { return selectListEntry(state, repository, branch); }; - -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[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 fd33efbc7b..6d88315b04 100644 --- a/scm-ui/src/repos/modules/changesets.test.js +++ b/scm-ui/src/repos/modules/changesets.test.js @@ -9,9 +9,6 @@ import reducer, { FETCH_CHANGESETS_PENDING, FETCH_CHANGESETS_SUCCESS, fetchChangesets, - fetchChangesetsByBranch, - fetchChangesetsByBranchAndPage, - fetchChangesetsByPage, fetchChangesetsSuccess, getChangesets, getFetchChangesetsFailure, @@ -68,7 +65,6 @@ describe("changesets", () => { const expectedActions = [ { type: FETCH_CHANGESETS_PENDING, - payload: { repository }, itemId: "foo/bar" }, { @@ -91,7 +87,6 @@ describe("changesets", () => { const expectedActions = [ { type: FETCH_CHANGESETS_PENDING, - payload: { repository, branch }, itemId }, { @@ -102,11 +97,9 @@ describe("changesets", () => { ]; const store = mockStore({}); - return store - .dispatch(fetchChangesetsByBranch(repository, branch)) - .then(() => { - expect(store.getActions()).toEqual(expectedActions); - }); + return store.dispatch(fetchChangesets(repository, branch)).then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); }); it("should fail fetching changesets on error", () => { @@ -116,7 +109,6 @@ describe("changesets", () => { const expectedActions = [ { type: FETCH_CHANGESETS_PENDING, - payload: { repository }, itemId } ]; @@ -136,19 +128,16 @@ describe("changesets", () => { const expectedActions = [ { type: FETCH_CHANGESETS_PENDING, - payload: { repository, branch }, itemId } ]; const store = mockStore({}); - return store - .dispatch(fetchChangesetsByBranch(repository, branch)) - .then(() => { - expect(store.getActions()[0]).toEqual(expectedActions[0]); - expect(store.getActions()[1].type).toEqual(FETCH_CHANGESETS_FAILURE); - expect(store.getActions()[1].payload).toBeDefined(); - }); + return store.dispatch(fetchChangesets(repository, branch)).then(() => { + expect(store.getActions()[0]).toEqual(expectedActions[0]); + expect(store.getActions()[1].type).toEqual(FETCH_CHANGESETS_FAILURE); + expect(store.getActions()[1].payload).toBeDefined(); + }); }); it("should fetch changesets by page", () => { @@ -157,7 +146,6 @@ describe("changesets", () => { const expectedActions = [ { type: FETCH_CHANGESETS_PENDING, - payload: { repository }, itemId: "foo/bar" }, { @@ -168,9 +156,11 @@ describe("changesets", () => { ]; const store = mockStore({}); - return store.dispatch(fetchChangesetsByPage(repository, 5)).then(() => { - expect(store.getActions()).toEqual(expectedActions); - }); + return store + .dispatch(fetchChangesets(repository, undefined, 5)) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); }); it("should fetch changesets by branch and page", () => { @@ -179,7 +169,6 @@ describe("changesets", () => { const expectedActions = [ { type: FETCH_CHANGESETS_PENDING, - payload: { repository, branch }, itemId: "foo/bar/specific" }, { @@ -190,11 +179,9 @@ describe("changesets", () => { ]; const store = mockStore({}); - return store - .dispatch(fetchChangesetsByBranchAndPage(repository, branch, 5)) - .then(() => { - expect(store.getActions()).toEqual(expectedActions); - }); + return store.dispatch(fetchChangesets(repository, branch, 5)).then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); }); }); @@ -220,19 +207,15 @@ describe("changesets", () => { it("should set state to received changesets", () => { const newState = reducer( {}, - fetchChangesetsSuccess(responseBody, repository) + fetchChangesetsSuccess(repository, undefined, responseBody) ); expect(newState).toBeDefined(); - expect(newState.byKey["foo/bar"].byId["changeset1"].author.mail).toEqual( + expect(newState["foo/bar"].byId["changeset1"].author.mail).toEqual( "z@phod.com" ); - expect(newState.byKey["foo/bar"].byId["changeset2"].description).toEqual( - "foo" - ); - expect(newState.byKey["foo/bar"].byId["changeset3"].description).toEqual( - "bar" - ); - expect(newState.byKey["foo/bar"].list).toEqual({ + expect(newState["foo/bar"].byId["changeset2"].description).toEqual("foo"); + expect(newState["foo/bar"].byId["changeset3"].description).toEqual("bar"); + expect(newState["foo/bar"].list).toEqual({ entry: { page: 1, pageTotal: 10, @@ -249,12 +232,10 @@ describe("changesets", () => { it("should get all changesets for a given repository", () => { const state = { changesets: { - byKey: { - "foo/bar": { - byId: { - id1: { id: "id1" }, - id2: { id: "id2" } - } + "foo/bar": { + byId: { + id1: { id: "id1" }, + id2: { id: "id2" } } } } From e841c0693473b9744815c998113a28cfb3cdd770 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 17 Oct 2018 10:57:01 +0200 Subject: [PATCH 65/81] fix paging url --- scm-ui/src/repos/containers/BranchRoot.js | 2 +- scm-ui/src/repos/containers/Changesets.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scm-ui/src/repos/containers/BranchRoot.js b/scm-ui/src/repos/containers/BranchRoot.js index 1a181ce0b8..208f1e266b 100644 --- a/scm-ui/src/repos/containers/BranchRoot.js +++ b/scm-ui/src/repos/containers/BranchRoot.js @@ -56,7 +56,7 @@ class BranchRoot extends React.Component { branchSelected = (branch: Branch) => { const url = this.matchedUrl(); this.props.history.push( - `${url}/${encodeURIComponent(branch.name)}/changesets` + `${url}/${encodeURIComponent(branch.name)}/changesets/` ); }; diff --git a/scm-ui/src/repos/containers/Changesets.js b/scm-ui/src/repos/containers/Changesets.js index 21417b6715..5c6934107e 100644 --- a/scm-ui/src/repos/containers/Changesets.js +++ b/scm-ui/src/repos/containers/Changesets.js @@ -79,9 +79,9 @@ class Changesets extends React.Component { }; renderPaginator = () => { - const { list } = this.props; + const { page, list } = this.props; if (list) { - return ; + return ; } return null; }; From e6c16c3fc5d8e48d1be889f79b244226ab6ce47b Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 17 Oct 2018 11:33:40 +0200 Subject: [PATCH 66/81] fixed branch selection urls and styles branch selector --- .../ui-components/src/LinkPaginator.js | 26 +-- scm-ui/public/locales/en/repos.json | 3 + scm-ui/src/repos/components/DropDown.js | 8 +- scm-ui/src/repos/containers/BranchChooser.js | 166 ------------------ scm-ui/src/repos/containers/BranchRoot.js | 21 ++- scm-ui/src/repos/containers/BranchSelector.js | 61 +++++-- scm-ui/src/repos/containers/RepositoryRoot.js | 15 +- 7 files changed, 92 insertions(+), 208 deletions(-) delete mode 100644 scm-ui/src/repos/containers/BranchChooser.js diff --git a/scm-ui-components/packages/ui-components/src/LinkPaginator.js b/scm-ui-components/packages/ui-components/src/LinkPaginator.js index 4e21033f93..aaf13d7b15 100644 --- a/scm-ui-components/packages/ui-components/src/LinkPaginator.js +++ b/scm-ui-components/packages/ui-components/src/LinkPaginator.js @@ -3,16 +3,16 @@ 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 + page: number, + + // context props + t: string => string }; class LinkPaginator extends React.Component { - //TODO: HATEOAS-Links verwenden renderFirstButton() { return ( @@ -26,30 +26,32 @@ class LinkPaginator extends React.Component { } renderPreviousButton(label?: string) { - const { match } = this.props; - const page = parseInt(match.params.page) || 1; + const { page } = this.props; const previousPage = page - 1; return (