From 9e489cfd1da6b9ee7a0c039f0c99ec9887ad498c Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Tue, 11 Sep 2018 17:20:30 +0200 Subject: [PATCH] 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 }} /> + );