Merged 2.0.0-m3

This commit is contained in:
Philipp Czora
2018-10-19 08:44:03 +02:00
27 changed files with 1002 additions and 229 deletions

View File

@@ -1,6 +1,6 @@
//@flow //@flow
import React from "react"; import React from "react";
import { withContextPath } from "./urls"; import {withContextPath} from "./urls";
type Props = { type Props = {
src: string, src: string,
@@ -9,9 +9,18 @@ type Props = {
}; };
class Image extends React.Component<Props> { class Image extends React.Component<Props> {
createImageSrc = () => {
const { src } = this.props;
if (src.startsWith("http")) {
return src;
}
return withContextPath(src);
};
render() { render() {
const { src, alt, className } = this.props; const { alt, className } = this.props;
return <img className={className} src={withContextPath(src)} alt={alt} />; return <img className={className} src={this.createImageSrc()} alt={alt} />;
} }
} }

View File

@@ -1,6 +1,6 @@
//@flow //@flow
import * as React from "react"; import * as React from "react";
import { Route, Link } from "react-router-dom"; import {Link, Route} from "react-router-dom";
// TODO mostly copy of PrimaryNavigationLink // TODO mostly copy of PrimaryNavigationLink

View File

@@ -5,6 +5,21 @@ export function withContextPath(path: string) {
return contextPath + path; return contextPath + path;
} }
export function withEndingSlash(url: string) {
if (url.endsWith("/")) {
return url;
}
return url + "/";
}
export function concat(base: string, ...parts: string[]) {
let url = base;
for ( let p of parts) {
url = withEndingSlash(url) + p;
}
return url;
}
export function getPageFromMatch(match: any) { export function getPageFromMatch(match: any) {
let page = parseInt(match.params.page, 10); let page = parseInt(match.params.page, 10);
if (isNaN(page) || !page) { if (isNaN(page) || !page) {

View File

@@ -1,5 +1,27 @@
// @flow // @flow
import { getPageFromMatch } from "./urls"; import {concat, getPageFromMatch, withEndingSlash} from "./urls";
describe("tests for withEndingSlash", () => {
it("should append missing slash", () => {
expect(withEndingSlash("abc")).toBe("abc/");
});
it("should not append a second slash", () => {
expect(withEndingSlash("abc/")).toBe("abc/");
});
});
describe("concat tests", () => {
it("should concat the parts to a single url", () => {
expect(concat("a")).toBe("a");
expect(concat("a", "b")).toBe("a/b");
expect(concat("a", "b", "c")).toBe("a/b/c");
});
});
describe("tests for getPageFromMatch", () => { describe("tests for getPageFromMatch", () => {
function createMatch(page: string) { function createMatch(page: string) {

View File

@@ -1,14 +1,11 @@
//@flow //@flow
import type { Links } from "./hal"; import type {Links} from "./hal";
export type Permission = { export type Permission = PermissionCreateEntry & {
name: string, _links: Links
type: string,
groupPermission: boolean,
_links?: Links
}; };
export type PermissionEntry = { export type PermissionCreateEntry = {
name: string, name: string,
type: string, type: string,
groupPermission: boolean groupPermission: boolean

View File

@@ -17,4 +17,4 @@ export type { Tag } from "./Tags";
export type { Config } from "./Config"; export type { Config } from "./Config";
export type { Permission, PermissionEntry, PermissionCollection } from "./RepositoryPermissions"; export type { Permission, PermissionCreateEntry, PermissionCollection } from "./RepositoryPermissions";

View File

@@ -53,7 +53,7 @@
"description": "Description", "description": "Description",
"contact": "Contact", "contact": "Contact",
"date": "Date", "date": "Date",
"summary": "Changeset {{id}} committed {{time}}" "summary": "Changeset {{id}} was committed {{time}}"
}, },
"author": { "author": {
"name": "Author", "name": "Author",

View File

@@ -0,0 +1,32 @@
//@flow
import React from "react";
import {binder} 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 AvatarImage extends React.Component<Props> {
render() {
const { changeset } = this.props;
const avatarFactory = binder.getExtension("changeset.avatar-factory");
if (avatarFactory) {
const avatar = avatarFactory(changeset);
return (
<Image
className="has-rounded-border"
src={avatar}
alt={changeset.author.name}
/>
);
}
return null;
}
}
export default AvatarImage;

View File

@@ -0,0 +1,18 @@
//@flow
import * as React from "react";
import {binder} from "@scm-manager/ui-extensions";
type Props = {
children: React.Node
};
class AvatarWrapper extends React.Component<Props> {
render() {
if (binder.hasExtension("changeset.avatar-factory")) {
return <>{this.props.children}</>;
}
return null;
}
}
export default AvatarWrapper;

View File

@@ -1,30 +0,0 @@
//@flow
import React from "react";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import type { Changeset } from "@scm-manager/ui-types";
type Props = {
changeset: Changeset
};
class ChangesetAvatar extends React.Component<Props> {
render() {
const { changeset } = this.props;
return (
<ExtensionPoint
name="repos.changeset-table.information"
renderAll={true}
props={{ changeset }}
>
{/* extension should render something like this: */}
{/* <div className="image is-64x64"> */}
{/* <figure className="media-left"> */}
{/* <Image src="/some/image.jpg" alt="Logo" /> */}
{/* </figure> */}
{/* </div> */}
</ExtensionPoint>
);
}
}
export default ChangesetAvatar;

View File

@@ -0,0 +1,97 @@
//@flow
import React from "react";
import type {Changeset, Repository} from "../../../../../scm-ui-components/packages/ui-types/src/index";
import {Interpolate, translate} from "react-i18next";
import injectSheet from "react-jss";
import ChangesetTag from "./ChangesetTag";
import ChangesetAuthor from "./ChangesetAuthor";
import {parseDescription} from "./changesets";
import {DateFromNow} from "../../../../../scm-ui-components/packages/ui-components/src/index";
import AvatarWrapper from "./AvatarWrapper";
import AvatarImage from "./AvatarImage";
import classNames from "classnames";
import ChangesetId from "./ChangesetId";
import type {Tag} from "@scm-manager/ui-types";
const styles = {
spacing: {
marginRight: "1em"
}
};
type Props = {
changeset: Changeset,
repository: Repository,
t: string => string,
classes: any
};
class ChangesetDetails extends React.Component<Props> {
render() {
const { changeset, repository, classes } = this.props;
const description = parseDescription(changeset.description);
const id = (
<ChangesetId repository={repository} changeset={changeset} link={false} />
);
const date = <DateFromNow date={changeset.date} />;
return (
<div className="content">
<h4>{description.title}</h4>
<article className="media">
<AvatarWrapper>
<p className={classNames("image", "is-64x64", classes.spacing)}>
<AvatarImage changeset={changeset} />
</p>
</AvatarWrapper>
<div className="media-content">
<p>
<ChangesetAuthor changeset={changeset} />
</p>
<p>
<Interpolate
i18nKey="changesets.changeset.summary"
id={id}
time={date}
/>
</p>
</div>
<div className="media-right">{this.renderTags()}</div>
</article>
<p>
{description.message.split("\n").map((item, key) => {
return (
<span key={key}>
{item}
<br />
</span>
);
})}
</p>
</div>
);
}
getTags = () => {
const { changeset } = this.props;
return changeset._embedded.tags || [];
};
renderTags = () => {
const tags = this.getTags();
if (tags.length > 0) {
return (
<div className="level-item">
{tags.map((tag: Tag) => {
return <ChangesetTag key={tag.name} tag={tag} />;
})}
</div>
);
}
return null;
};
}
export default injectSheet(styles)(translate("repos")(ChangesetDetails));

View File

@@ -1,25 +1,47 @@
//@flow //@flow
import { Link } from "react-router-dom"; import {Link} from "react-router-dom";
import React from "react"; import React from "react";
import type { Repository, Changeset } from "@scm-manager/ui-types"; import type {Changeset, Repository} from "@scm-manager/ui-types";
type Props = { type Props = {
repository: Repository, repository: Repository,
changeset: Changeset changeset: Changeset,
link: boolean
}; };
export default class ChangesetId extends React.Component<Props> { export default class ChangesetId extends React.Component<Props> {
render() { static defaultProps = {
const { repository, changeset } = this.props; link: true
};
shortId = (changeset: Changeset) => {
return changeset.id.substr(0, 7);
};
renderLink = () => {
const { changeset, repository } = this.props;
return ( return (
<Link <Link
to={`/repo/${repository.namespace}/${repository.name}/changeset/${ to={`/repo/${repository.namespace}/${repository.name}/changeset/${
changeset.id changeset.id
}`} }`}
> >
{changeset.id.substr(0, 7)} {this.shortId(changeset)}
</Link> </Link>
); );
};
renderText = () => {
const { changeset } = this.props;
return this.shortId(changeset);
};
render() {
const { link } = this.props;
if (link) {
return this.renderLink();
}
return this.renderText();
} }
} }

View File

@@ -1,15 +1,17 @@
//@flow //@flow
import React from "react"; import React from "react";
import type { Changeset, Repository, Tag } from "@scm-manager/ui-types"; import type {Changeset, Repository, Tag} from "@scm-manager/ui-types";
import classNames from "classnames"; import classNames from "classnames";
import { translate, Interpolate } from "react-i18next"; import {Interpolate, translate} from "react-i18next";
import ChangesetAvatar from "./ChangesetAvatar";
import ChangesetId from "./ChangesetId"; import ChangesetId from "./ChangesetId";
import injectSheet from "react-jss"; import injectSheet from "react-jss";
import { DateFromNow } from "@scm-manager/ui-components"; import {DateFromNow} from "@scm-manager/ui-components";
import ChangesetAuthor from "./ChangesetAuthor"; import ChangesetAuthor from "./ChangesetAuthor";
import ChangesetTag from "./ChangesetTag"; import ChangesetTag from "./ChangesetTag";
import { compose } from "redux"; import {compose} from "redux";
import {parseDescription} from "./changesets";
import AvatarWrapper from "./AvatarWrapper";
import AvatarImage from "./AvatarImage";
const styles = { const styles = {
pointer: { pointer: {
@@ -46,14 +48,23 @@ class ChangesetRow extends React.Component<Props> {
const changesetLink = this.createLink(changeset); const changesetLink = this.createLink(changeset);
const dateFromNow = <DateFromNow date={changeset.date} />; const dateFromNow = <DateFromNow date={changeset.date} />;
const authorLine = <ChangesetAuthor changeset={changeset} />; const authorLine = <ChangesetAuthor changeset={changeset} />;
const description = parseDescription(changeset.description);
return ( return (
<article className={classNames("media", classes.inner)}> <article className={classNames("media", classes.inner)}>
<ChangesetAvatar changeset={changeset} /> <AvatarWrapper>
<div>
<figure className="media-left">
<p className="image is-64x64">
<AvatarImage changeset={changeset} />
</p>
</figure>
</div>
</AvatarWrapper>
<div className={classNames("media-content", classes.withOverflow)}> <div className={classNames("media-content", classes.withOverflow)}>
<div className="content"> <div className="content">
<p className="is-ellipsis-overflow"> <p className="is-ellipsis-overflow">
{changeset.description} <strong>{description.title}</strong>
<br /> <br />
<Interpolate <Interpolate
i18nKey="changesets.changeset.summary" i18nKey="changesets.changeset.summary"

View File

@@ -0,0 +1,24 @@
// @flow
export type Description = {
title: string,
message: string
};
export function parseDescription(description: string): Description {
const lineBreak = description.indexOf("\n");
let title;
let message = "";
if (lineBreak > 0) {
title = description.substring(0, lineBreak);
message = description.substring(lineBreak + 1);
} else {
title = description;
}
return {
title,
message
};
}

View File

@@ -0,0 +1,16 @@
// @flow
import {parseDescription} from "./changesets";
describe("parseDescription tests", () => {
it("should return a description with title and message", () => {
const desc = parseDescription("Hello\nTrillian");
expect(desc.title).toBe("Hello");
expect(desc.message).toBe("Trillian");
});
it("should return a description with title and without message", () => {
const desc = parseDescription("Hello Trillian");
expect(desc.title).toBe("Hello Trillian");
});
});

View File

@@ -0,0 +1,75 @@
//@flow
import React from "react";
import { connect } from "react-redux";
import { withRouter } from "react-router-dom";
import type { Changeset, Repository } from "@scm-manager/ui-types";
import {
fetchChangesetIfNeeded,
getChangeset,
getFetchChangesetFailure,
isFetchChangesetPending
} from "../modules/changesets";
import ChangesetDetails from "../components/changesets/ChangesetDetails";
import { translate } from "react-i18next";
import { ErrorPage, Loading } from "@scm-manager/ui-components";
type Props = {
id: string,
changeset: Changeset,
repository: Repository,
loading: boolean,
error: Error,
fetchChangesetIfNeeded: (repository: Repository, id: string) => void,
match: any,
t: string => string
};
class ChangesetView extends React.Component<Props> {
componentDidMount() {
const { fetchChangesetIfNeeded, repository } = this.props;
const id = this.props.match.params.id;
fetchChangesetIfNeeded(repository, id);
}
render() {
const { changeset, loading, error, t, repository } = this.props;
if (error) {
return (
<ErrorPage
title={t("changeset-error.title")}
subtitle={t("changeset-error.subtitle")}
error={error}
/>
);
}
if (!changeset || loading) return <Loading />;
return <ChangesetDetails changeset={changeset} repository={repository} />;
}
}
const mapStateToProps = (state, ownProps: Props) => {
const repository = ownProps.repository;
const id = ownProps.match.params.id;
const changeset = getChangeset(state, repository, id);
const loading = isFetchChangesetPending(state, repository, id);
const error = getFetchChangesetFailure(state, repository, id);
return { changeset, error, loading };
};
const mapDispatchToProps = dispatch => {
return {
fetchChangesetIfNeeded: (repository: Repository, id: string) => {
dispatch(fetchChangesetIfNeeded(repository, id));
}
};
};
export default withRouter(
connect(
mapStateToProps,
mapDispatchToProps
)(translate("changesets")(ChangesetView))
);

View File

@@ -1,13 +1,8 @@
// @flow // @flow
import React from "react"; import React from "react";
import { withRouter } from "react-router-dom"; import {withRouter} from "react-router-dom";
import type { import type {Branch, Changeset, PagedCollection, Repository} from "@scm-manager/ui-types";
Branch,
Changeset,
PagedCollection,
Repository
} from "@scm-manager/ui-types";
import { import {
fetchChangesets, fetchChangesets,
getChangesets, getChangesets,
@@ -16,15 +11,10 @@ import {
selectListAsCollection selectListAsCollection
} from "../modules/changesets"; } from "../modules/changesets";
import { connect } from "react-redux"; import {connect} from "react-redux";
import ChangesetList from "../components/changesets/ChangesetList"; import ChangesetList from "../components/changesets/ChangesetList";
import { import {ErrorNotification, getPageFromMatch, LinkPaginator, Loading} from "@scm-manager/ui-components";
ErrorNotification, import {compose} from "redux";
LinkPaginator,
Loading,
getPageFromMatch
} from "@scm-manager/ui-components";
import { compose } from "redux";
type Props = { type Props = {
repository: Repository, repository: Repository,

View File

@@ -1,32 +1,23 @@
//@flow //@flow
import React from "react"; import React from "react";
import { import {deleteRepo, fetchRepo, getFetchRepoFailure, getRepository, isFetchRepoPending} from "../modules/repos";
deleteRepo,
fetchRepo, import {connect} from "react-redux";
getFetchRepoFailure, import {Route, Switch} from "react-router-dom";
getRepository, import type {Repository} from "@scm-manager/ui-types";
isFetchRepoPending
} from "../modules/repos"; import {ErrorPage, Loading, Navigation, NavLink, Page, Section} from "@scm-manager/ui-components";
import { connect } from "react-redux"; import {translate} from "react-i18next";
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 RepositoryDetails from "../components/RepositoryDetails";
import DeleteNavAction from "../components/DeleteNavAction"; import DeleteNavAction from "../components/DeleteNavAction";
import Edit from "../containers/Edit"; import Edit from "../containers/Edit";
import Permissions from "../permissions/containers/Permissions"; import Permissions from "../permissions/containers/Permissions";
import type { History } from "history"; import type {History} from "history";
import EditNavLink from "../components/EditNavLink"; import EditNavLink from "../components/EditNavLink";
import BranchRoot from "./BranchRoot"; import BranchRoot from "./BranchRoot";
import ChangesetView from "./ChangesetView";
import PermissionsNavLink from "../components/PermissionsNavLink"; import PermissionsNavLink from "../components/PermissionsNavLink";
import ScmDiff from "./ScmDiff"; import ScmDiff from "./ScmDiff";
@@ -73,6 +64,11 @@ class RepositoryRoot extends React.Component<Props> {
this.props.deleteRepo(repository, this.deleted); this.props.deleteRepo(repository, this.deleted);
}; };
matchChangeset = (route: any) => {
const url = this.matchedUrl();
return route.location.pathname.match(`${url}/changeset/`);
};
matches = (route: any) => { matches = (route: any) => {
const url = this.matchedUrl(); const url = this.matchedUrl();
const regex = new RegExp(`${url}(/branches)?/?[^/]*/changesets?.*`); const regex = new RegExp(`${url}(/branches)?/?[^/]*/changesets?.*`);
@@ -120,6 +116,11 @@ class RepositoryRoot extends React.Component<Props> {
/> />
)} )}
/> />
<Route
exact
path={`${url}/changeset/:id`}
render={() => <ChangesetView repository={repository} />}
/>
<Route <Route
path={`${url}/changesets`} path={`${url}/changesets`}
render={() => ( render={() => (
@@ -145,7 +146,7 @@ class RepositoryRoot extends React.Component<Props> {
component={() => ( component={() => (
<ScmDiff <ScmDiff
repository={repository} repository={repository}
revision={"2"} // TODO: this is hardcoded only for dev purposes. revision={"a801749dc445d9d71e3fe4c50241433a2adfba6a"} // TODO: this is hardcoded only for dev purposes.
sideBySide={false} sideBySide={false}
/> />
)} )}

View File

@@ -1,27 +1,86 @@
// @flow // @flow
import { import {FAILURE_SUFFIX, PENDING_SUFFIX, SUCCESS_SUFFIX} from "../../modules/types";
FAILURE_SUFFIX, import {apiClient, urls} from "@scm-manager/ui-components";
PENDING_SUFFIX, import {isPending} from "../../modules/pending";
SUCCESS_SUFFIX import {getFailure} from "../../modules/failure";
} from "../../modules/types"; import type {Action, Branch, PagedCollection, Repository} from "@scm-manager/ui-types";
import { apiClient } from "@scm-manager/ui-components";
import { isPending } from "../../modules/pending";
import { getFailure } from "../../modules/failure";
import type {
Action,
Branch,
PagedCollection,
Repository
} from "@scm-manager/ui-types";
export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS"; export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS";
export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`;
export const FETCH_CHANGESETS_SUCCESS = `${FETCH_CHANGESETS}_${SUCCESS_SUFFIX}`; export const FETCH_CHANGESETS_SUCCESS = `${FETCH_CHANGESETS}_${SUCCESS_SUFFIX}`;
export const FETCH_CHANGESETS_FAILURE = `${FETCH_CHANGESETS}_${FAILURE_SUFFIX}`; export const FETCH_CHANGESETS_FAILURE = `${FETCH_CHANGESETS}_${FAILURE_SUFFIX}`;
//TODO: Content type export const FETCH_CHANGESET = "scm/repos/FETCH_CHANGESET";
export const FETCH_CHANGESET_PENDING = `${FETCH_CHANGESET}_${PENDING_SUFFIX}`;
export const FETCH_CHANGESET_SUCCESS = `${FETCH_CHANGESET}_${SUCCESS_SUFFIX}`;
export const FETCH_CHANGESET_FAILURE = `${FETCH_CHANGESET}_${FAILURE_SUFFIX}`;
// actions // actions
//TODO: Content type
export function fetchChangesetIfNeeded(repository: Repository, id: string) {
return (dispatch: any, getState: any) => {
if (shouldFetchChangeset(getState(), repository, id)) {
return dispatch(fetchChangeset(repository, id));
}
};
}
export function fetchChangeset(repository: Repository, id: string) {
return function(dispatch: any) {
dispatch(fetchChangesetPending(repository, id));
return apiClient
.get(createChangesetUrl(repository, id))
.then(response => response.json())
.then(data => dispatch(fetchChangesetSuccess(data, repository, id)))
.catch(err => {
dispatch(fetchChangesetFailure(repository, id, err));
});
};
}
function createChangesetUrl(repository: Repository, id: string) {
return urls.concat(repository._links.changesets.href, id);
}
export function fetchChangesetPending(
repository: Repository,
id: string
): Action {
return {
type: FETCH_CHANGESET_PENDING,
itemId: createChangesetItemId(repository, id)
};
}
export function fetchChangesetSuccess(
changeset: any,
repository: Repository,
id: string
): Action {
return {
type: FETCH_CHANGESET_SUCCESS,
payload: { changeset, repository, id },
itemId: createChangesetItemId(repository, id)
};
}
function fetchChangesetFailure(
repository: Repository,
id: string,
error: Error
): Action {
return {
type: FETCH_CHANGESET_FAILURE,
payload: {
repository,
id,
error
},
itemId: createChangesetItemId(repository, id)
};
}
export function fetchChangesets( export function fetchChangesets(
repository: Repository, repository: Repository,
@@ -80,7 +139,11 @@ export function fetchChangesetsSuccess(
): Action { ): Action {
return { return {
type: FETCH_CHANGESETS_SUCCESS, type: FETCH_CHANGESETS_SUCCESS,
payload: changesets, payload: {
repository,
branch,
changesets
},
itemId: createItemId(repository, branch) itemId: createItemId(repository, branch)
}; };
} }
@@ -101,6 +164,11 @@ function fetchChangesetsFailure(
}; };
} }
function createChangesetItemId(repository: Repository, id: string) {
const { namespace, name } = repository;
return namespace + "/" + name + "/" + id;
}
function createItemId(repository: Repository, branch?: Branch): string { function createItemId(repository: Repository, branch?: Branch): string {
const { namespace, name } = repository; const { namespace, name } = repository;
let itemId = namespace + "/" + name; let itemId = namespace + "/" + name;
@@ -118,10 +186,32 @@ export default function reducer(
if (!action.payload) { if (!action.payload) {
return state; return state;
} }
const payload = action.payload; const payload = action.payload;
switch (action.type) { switch (action.type) {
case FETCH_CHANGESET_SUCCESS:
const _key = createItemId(payload.repository);
let _oldByIds = {};
if (state[_key] && state[_key].byId) {
_oldByIds = state[_key].byId;
}
const changeset = payload.changeset;
return {
...state,
[_key]: {
...state[_key],
byId: {
..._oldByIds,
[changeset.id]: changeset
}
}
};
case FETCH_CHANGESETS_SUCCESS: case FETCH_CHANGESETS_SUCCESS:
const changesets = payload._embedded.changesets; const changesets = payload.changesets._embedded.changesets;
const changesetIds = changesets.map(c => c.id); const changesetIds = changesets.map(c => c.id);
const key = action.itemId; const key = action.itemId;
@@ -129,26 +219,32 @@ export default function reducer(
return state; return state;
} }
let oldByIds = {}; const repoId = createItemId(payload.repository);
if (state[key] && state[key].byId) {
oldByIds = state[key].byId; let oldState = {};
if (state[repoId]) {
oldState = state[repoId];
} }
const branchName = payload.branch ? payload.branch.name : "";
const byIds = extractChangesetsByIds(changesets); const byIds = extractChangesetsByIds(changesets);
return { return {
...state, ...state,
[key]: { [repoId]: {
byId: { byId: {
...oldByIds, ...oldState.byId,
...byIds ...byIds
}, },
list: { byBranch: {
entries: changesetIds, ...oldState.byBranch,
entry: { [branchName]: {
page: payload.page, entries: changesetIds,
pageTotal: payload.pageTotal, entry: {
_links: payload._links page: payload.changesets.page,
pageTotal: payload.changesets.pageTotal,
_links: payload.changesets._links
}
} }
} }
} }
@@ -174,17 +270,76 @@ export function getChangesets(
repository: Repository, repository: Repository,
branch?: Branch branch?: Branch
) { ) {
const key = createItemId(repository, branch); const repoKey = createItemId(repository);
const changesets = state.changesets[key]; const stateRoot = state.changesets[repoKey];
if (!stateRoot || !stateRoot.byBranch) {
return null;
}
const branchName = branch ? branch.name : "";
const changesets = stateRoot.byBranch[branchName];
if (!changesets) { if (!changesets) {
return null; return null;
} }
return changesets.list.entries.map((id: string) => {
return changesets.byId[id]; return changesets.entries.map((id: string) => {
return stateRoot.byId[id];
}); });
} }
export function getChangeset(
state: Object,
repository: Repository,
id: string
) {
const key = createItemId(repository);
const changesets =
state.changesets && state.changesets[key]
? state.changesets[key].byId
: null;
if (changesets != null && changesets[id]) {
return changesets[id];
}
return null;
}
export function shouldFetchChangeset(
state: Object,
repository: Repository,
id: string
) {
if (getChangeset(state, repository, id)) {
return false;
}
return true;
}
export function isFetchChangesetPending(
state: Object,
repository: Repository,
id: string
) {
return isPending(
state,
FETCH_CHANGESET,
createChangesetItemId(repository, id)
);
}
export function getFetchChangesetFailure(
state: Object,
repository: Repository,
id: string
) {
return getFailure(
state,
FETCH_CHANGESET,
createChangesetItemId(repository, id)
);
}
export function isFetchChangesetsPending( export function isFetchChangesetsPending(
state: Object, state: Object,
repository: Repository, repository: Repository,
@@ -202,9 +357,15 @@ export function getFetchChangesetsFailure(
} }
const selectList = (state: Object, repository: Repository, branch?: Branch) => { const selectList = (state: Object, repository: Repository, branch?: Branch) => {
const itemId = createItemId(repository, branch); const repoId = createItemId(repository);
if (state.changesets[itemId] && state.changesets[itemId].list) {
return state.changesets[itemId].list; const branchName = branch ? branch.name : "";
if (state.changesets[repoId]) {
const repoState = state.changesets[repoId];
if (repoState.byBranch && repoState.byBranch[branchName]) {
return repoState.byBranch[branchName];
}
} }
return {}; return {};
}; };

View File

@@ -4,15 +4,27 @@ import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk"; import thunk from "redux-thunk";
import fetchMock from "fetch-mock"; import fetchMock from "fetch-mock";
import reducer, { import reducer, {
FETCH_CHANGESET,
FETCH_CHANGESET_FAILURE,
FETCH_CHANGESET_PENDING,
FETCH_CHANGESET_SUCCESS,
FETCH_CHANGESETS, FETCH_CHANGESETS,
FETCH_CHANGESETS_FAILURE, FETCH_CHANGESETS_FAILURE,
FETCH_CHANGESETS_PENDING, FETCH_CHANGESETS_PENDING,
FETCH_CHANGESETS_SUCCESS, FETCH_CHANGESETS_SUCCESS,
fetchChangeset,
fetchChangesetIfNeeded,
fetchChangesets, fetchChangesets,
fetchChangesetsSuccess, fetchChangesetsSuccess,
fetchChangesetSuccess,
getChangeset,
getChangesets, getChangesets,
getFetchChangesetFailure,
getFetchChangesetsFailure, getFetchChangesetsFailure,
isFetchChangesetsPending isFetchChangesetPending,
isFetchChangesetsPending,
selectListAsCollection,
shouldFetchChangeset
} from "./changesets"; } from "./changesets";
const branch = { const branch = {
@@ -21,7 +33,7 @@ const branch = {
_links: { _links: {
history: { history: {
href: href:
"http://scm/api/rest/v2/repositories/foo/bar/branches/specific/changesets" "http://scm.hitchhicker.com/api/v2/repositories/foo/bar/branches/specific/changesets"
} }
} }
}; };
@@ -32,14 +44,14 @@ const repository = {
type: "GIT", type: "GIT",
_links: { _links: {
self: { self: {
href: "http://scm/api/rest/v2/repositories/foo/bar" href: "http://scm.hitchhicker.com/api/v2/repositories/foo/bar"
}, },
changesets: { changesets: {
href: "http://scm/api/rest/v2/repositories/foo/bar/changesets" href: "http://scm.hitchhicker.com/api/v2/repositories/foo/bar/changesets"
}, },
branches: { branches: {
href: href:
"http://scm/api/rest/v2/repositories/foo/bar/branches/specific/branches" "http://scm.hitchhicker.com/api/v2/repositories/foo/bar/branches/specific/branches"
} }
} }
}; };
@@ -49,9 +61,10 @@ const changesets = {};
describe("changesets", () => { describe("changesets", () => {
describe("fetching of changesets", () => { describe("fetching of changesets", () => {
const DEFAULT_BRANCH_URL = const DEFAULT_BRANCH_URL =
"http://scm/api/rest/v2/repositories/foo/bar/changesets"; "http://scm.hitchhicker.com/api/v2/repositories/foo/bar/changesets";
const SPECIFIC_BRANCH_URL = const SPECIFIC_BRANCH_URL =
"http://scm/api/rest/v2/repositories/foo/bar/branches/specific/changesets"; "http://scm.hitchhicker.com/api/v2/repositories/foo/bar/branches/specific/changesets";
const mockStore = configureMockStore([thunk]); const mockStore = configureMockStore([thunk]);
afterEach(() => { afterEach(() => {
@@ -59,6 +72,102 @@ describe("changesets", () => {
fetchMock.restore(); fetchMock.restore();
}); });
const changesetId = "aba876c0625d90a6aff1494f3d161aaa7008b958";
it("should fetch changeset", () => {
fetchMock.getOnce(DEFAULT_BRANCH_URL + "/" + changesetId, "{}");
const expectedActions = [
{
type: FETCH_CHANGESET_PENDING,
itemId: "foo/bar/" + changesetId
},
{
type: FETCH_CHANGESET_SUCCESS,
payload: {
changeset: {},
id: changesetId,
repository: repository
},
itemId: "foo/bar/" + changesetId
}
];
const store = mockStore({});
return store
.dispatch(fetchChangeset(repository, changesetId))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
it("should fail fetching changeset on error", () => {
fetchMock.getOnce(DEFAULT_BRANCH_URL + "/" + changesetId, 500);
const expectedActions = [
{
type: FETCH_CHANGESET_PENDING,
itemId: "foo/bar/" + changesetId
}
];
const store = mockStore({});
return store
.dispatch(fetchChangeset(repository, changesetId))
.then(() => {
expect(store.getActions()[0]).toEqual(expectedActions[0]);
expect(store.getActions()[1].type).toEqual(FETCH_CHANGESET_FAILURE);
expect(store.getActions()[1].payload).toBeDefined();
});
});
it("should fetch changeset if needed", () => {
fetchMock.getOnce(DEFAULT_BRANCH_URL + "/id3", "{}");
const expectedActions = [
{
type: FETCH_CHANGESET_PENDING,
itemId: "foo/bar/id3"
},
{
type: FETCH_CHANGESET_SUCCESS,
payload: {
changeset: {},
id: "id3",
repository: repository
},
itemId: "foo/bar/id3"
}
];
const store = mockStore({});
return store
.dispatch(fetchChangesetIfNeeded(repository, "id3"))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
it("should not fetch changeset if not needed", () => {
fetchMock.getOnce(DEFAULT_BRANCH_URL + "/id1", 500);
const state = {
changesets: {
"foo/bar": {
byId: {
id1: { id: "id1" },
id2: { id: "id2" }
}
}
}
};
const store = mockStore(state);
return expect(
store.dispatch(fetchChangesetIfNeeded(repository, "id1"))
).toEqual(undefined);
});
it("should fetch changesets for default branch", () => { it("should fetch changesets for default branch", () => {
fetchMock.getOnce(DEFAULT_BRANCH_URL, "{}"); fetchMock.getOnce(DEFAULT_BRANCH_URL, "{}");
@@ -69,7 +178,11 @@ describe("changesets", () => {
}, },
{ {
type: FETCH_CHANGESETS_SUCCESS, type: FETCH_CHANGESETS_SUCCESS,
payload: changesets, payload: {
repository,
undefined,
changesets
},
itemId: "foo/bar" itemId: "foo/bar"
} }
]; ];
@@ -91,7 +204,11 @@ describe("changesets", () => {
}, },
{ {
type: FETCH_CHANGESETS_SUCCESS, type: FETCH_CHANGESETS_SUCCESS,
payload: changesets, payload: {
repository,
branch,
changesets
},
itemId itemId
} }
]; ];
@@ -150,7 +267,11 @@ describe("changesets", () => {
}, },
{ {
type: FETCH_CHANGESETS_SUCCESS, type: FETCH_CHANGESETS_SUCCESS,
payload: changesets, payload: {
repository,
undefined,
changesets
},
itemId: "foo/bar" itemId: "foo/bar"
} }
]; ];
@@ -173,7 +294,11 @@ describe("changesets", () => {
}, },
{ {
type: FETCH_CHANGESETS_SUCCESS, type: FETCH_CHANGESETS_SUCCESS,
payload: changesets, payload: {
repository,
branch,
changesets
},
itemId: "foo/bar/specific" itemId: "foo/bar/specific"
} }
]; ];
@@ -215,7 +340,7 @@ describe("changesets", () => {
); );
expect(newState["foo/bar"].byId["changeset2"].description).toEqual("foo"); expect(newState["foo/bar"].byId["changeset2"].description).toEqual("foo");
expect(newState["foo/bar"].byId["changeset3"].description).toEqual("bar"); expect(newState["foo/bar"].byId["changeset3"].description).toEqual("bar");
expect(newState["foo/bar"].list).toEqual({ expect(newState["foo/bar"].byBranch[""]).toEqual({
entry: { entry: {
page: 1, page: 1,
pageTotal: 10, pageTotal: 10,
@@ -225,6 +350,20 @@ describe("changesets", () => {
}); });
}); });
it("should store the changeset list to branch", () => {
const newState = reducer(
{},
fetchChangesetsSuccess(repository, branch, responseBody)
);
expect(newState["foo/bar"].byId["changeset1"]).toBeDefined();
expect(newState["foo/bar"].byBranch["specific"].entries).toEqual([
"changeset1",
"changeset2",
"changeset3"
]);
});
it("should not remove existing changesets", () => { it("should not remove existing changesets", () => {
const state = { const state = {
"foo/bar": { "foo/bar": {
@@ -232,8 +371,10 @@ describe("changesets", () => {
id2: { id: "id2" }, id2: { id: "id2" },
id1: { id: "id1" } id1: { id: "id1" }
}, },
list: { byBranch: {
entries: ["id1", "id2"] "": {
entries: ["id1", "id2"]
}
} }
} }
}; };
@@ -245,7 +386,7 @@ describe("changesets", () => {
const fooBar = newState["foo/bar"]; const fooBar = newState["foo/bar"];
expect(fooBar.list.entries).toEqual([ expect(fooBar.byBranch[""].entries).toEqual([
"changeset1", "changeset1",
"changeset2", "changeset2",
"changeset3" "changeset3"
@@ -253,11 +394,154 @@ describe("changesets", () => {
expect(fooBar.byId["id2"]).toEqual({ id: "id2" }); expect(fooBar.byId["id2"]).toEqual({ id: "id2" });
expect(fooBar.byId["id1"]).toEqual({ id: "id1" }); expect(fooBar.byId["id1"]).toEqual({ id: "id1" });
}); });
const responseBodySingleChangeset = {
id: "id3",
author: {
mail: "z@phod.com",
name: "zaphod"
},
date: "2018-09-13T08:46:22Z",
description: "added testChangeset",
_links: {},
_embedded: {
tags: [],
branches: []
}
};
it("should add changeset to state", () => {
const newState = reducer(
{
"foo/bar": {
byId: {
"id2": {
id: "id2",
author: { mail: "mail@author.com", name: "author" }
}
},
list: {
entry: {
page: 1,
pageTotal: 10,
_links: {}
},
entries: ["id2"]
}
}
},
fetchChangesetSuccess(responseBodySingleChangeset, repository, "id3")
);
expect(newState).toBeDefined();
expect(newState["foo/bar"].byId["id3"].description).toEqual(
"added testChangeset"
);
expect(newState["foo/bar"].byId["id3"].author.mail).toEqual("z@phod.com");
expect(newState["foo/bar"].byId["id2"]).toBeDefined();
expect(newState["foo/bar"].byId["id3"]).toBeDefined();
expect(newState["foo/bar"].list).toEqual({
entry: {
page: 1,
pageTotal: 10,
_links: {}
},
entries: ["id2"]
});
});
}); });
describe("changeset selectors", () => { describe("changeset selectors", () => {
const error = new Error("Something went wrong"); const error = new Error("Something went wrong");
it("should return changeset", () => {
const state = {
changesets: {
"foo/bar": {
byId: {
id1: { id: "id1" },
id2: { id: "id2" }
}
}
}
};
const result = getChangeset(state, repository, "id1");
expect(result).toEqual({ id: "id1" });
});
it("should return null if changeset does not exist", () => {
const state = {
changesets: {
"foo/bar": {
byId: {
id1: { id: "id1" },
id2: { id: "id2" }
}
}
}
};
const result = getChangeset(state, repository, "id3");
expect(result).toEqual(null);
});
it("should return true if changeset does not exist", () => {
const state = {
changesets: {
"foo/bar": {
byId: {
id1: { id: "id1" },
id2: { id: "id2" }
}
}
}
};
const result = shouldFetchChangeset(state, repository, "id3");
expect(result).toEqual(true);
});
it("should return false if changeset exists", () => {
const state = {
changesets: {
"foo/bar": {
byId: {
id1: { id: "id1" },
id2: { id: "id2" }
}
}
}
};
const result = shouldFetchChangeset(state, repository, "id2");
expect(result).toEqual(false);
});
it("should return true, when fetching changeset is pending", () => {
const state = {
pending: {
[FETCH_CHANGESET + "/foo/bar/id1"]: true
}
};
expect(isFetchChangesetPending(state, repository, "id1")).toBeTruthy();
});
it("should return false, when fetching changeset is not pending", () => {
expect(isFetchChangesetPending({}, repository, "id1")).toEqual(false);
});
it("should return error if fetching changeset failed", () => {
const state = {
failure: {
[FETCH_CHANGESET + "/foo/bar/id1"]: error
}
};
expect(getFetchChangesetFailure(state, repository, "id1")).toEqual(error);
});
it("should return false if fetching changeset did not fail", () => {
expect(getFetchChangesetFailure({}, repository, "id1")).toBeUndefined();
});
it("should get all changesets for a given repository", () => { it("should get all changesets for a given repository", () => {
const state = { const state = {
changesets: { changesets: {
@@ -266,8 +550,10 @@ describe("changesets", () => {
id2: { id: "id2" }, id2: { id: "id2" },
id1: { id: "id1" } id1: { id: "id1" }
}, },
list: { byBranch: {
entries: ["id1", "id2"] "": {
entries: ["id1", "id2"]
}
} }
} }
} }
@@ -303,5 +589,32 @@ describe("changesets", () => {
it("should return false if fetching changesets did not fail", () => { it("should return false if fetching changesets did not fail", () => {
expect(getFetchChangesetsFailure({}, repository)).toBeUndefined(); expect(getFetchChangesetsFailure({}, repository)).toBeUndefined();
}); });
it("should return list as collection for the default branch", () => {
const state = {
changesets: {
"foo/bar": {
byId: {
id2: { id: "id2" },
id1: { id: "id1" }
},
byBranch: {
"": {
entry: {
page: 1,
pageTotal: 10,
_links: {}
},
entries: ["id1", "id2"]
}
}
}
}
};
const collection = selectListAsCollection(state, repository);
expect(collection.page).toBe(1);
expect(collection.pageTotal).toBe(10);
});
}); });
}); });

View File

@@ -1,17 +1,14 @@
// @flow // @flow
import React from "react"; import React from "react";
import { translate } from "react-i18next"; import {translate} from "react-i18next";
import { Checkbox, InputField, SubmitButton } from "@scm-manager/ui-components"; import {Checkbox, InputField, SubmitButton} from "@scm-manager/ui-components";
import TypeSelector from "./TypeSelector"; import TypeSelector from "./TypeSelector";
import type { import type {PermissionCollection, PermissionCreateEntry} from "@scm-manager/ui-types";
PermissionCollection,
PermissionEntry
} from "@scm-manager/ui-types";
import * as validator from "./permissionValidation"; import * as validator from "./permissionValidation";
type Props = { type Props = {
t: string => string, t: string => string,
createPermission: (permission: PermissionEntry) => void, createPermission: (permission: PermissionCreateEntry) => void,
loading: boolean, loading: boolean,
currentPermissions: PermissionCollection currentPermissions: PermissionCollection
}; };
@@ -65,7 +62,7 @@ class CreatePermissionForm extends React.Component<Props, State> {
<SubmitButton <SubmitButton
label={t("permission.add-permission.submit-button")} label={t("permission.add-permission.submit-button")}
loading={loading} loading={loading}
disabled={!this.state.valid || this.state.name == ""} disabled={!this.state.valid || this.state.name === ""}
/> />
</form> </form>
</div> </div>

View File

@@ -1,21 +1,31 @@
// @flow // @flow
import { validation } from "@scm-manager/ui-components"; import {validation} from "@scm-manager/ui-components";
import type { import type {PermissionCollection} from "@scm-manager/ui-types";
PermissionCollection,
} from "@scm-manager/ui-types";
const isNameValid = validation.isNameValid; const isNameValid = validation.isNameValid;
export { isNameValid }; export { isNameValid };
export const isPermissionValid = (name: string, groupPermission: boolean, permissions: PermissionCollection) => { export const isPermissionValid = (
return isNameValid(name) && !currentPermissionIncludeName(name, groupPermission, permissions); name: string,
groupPermission: boolean,
permissions: PermissionCollection
) => {
return (
isNameValid(name) &&
!currentPermissionIncludeName(name, groupPermission, permissions)
);
}; };
const currentPermissionIncludeName = (name: string, groupPermission: boolean, permissions: PermissionCollection) => { const currentPermissionIncludeName = (
name: string,
groupPermission: boolean,
permissions: PermissionCollection
) => {
for (let i = 0; i < permissions.length; i++) { for (let i = 0; i < permissions.length; i++) {
if ( if (
permissions[i].name === name && permissions[i].name === name &&
permissions[i].groupPermission == groupPermission permissions[i].groupPermission === groupPermission
) )
return true; return true;
} }

View File

@@ -17,7 +17,8 @@ describe("permission validation", () => {
{ {
name: "PermissionName", name: "PermissionName",
groupPermission: true, groupPermission: true,
type: "READ" type: "READ",
_links: {}
} }
]; ];
const name = "PermissionName"; const name = "PermissionName";
@@ -33,7 +34,8 @@ describe("permission validation", () => {
{ {
name: "PermissionName", name: "PermissionName",
groupPermission: false, groupPermission: false,
type: "READ" type: "READ",
_links: {}
} }
]; ];
const name = "PermissionName"; const name = "PermissionName";

View File

@@ -1,31 +1,27 @@
//@flow //@flow
import React from "react"; import React from "react";
import { connect } from "react-redux"; import {connect} from "react-redux";
import { translate } from "react-i18next"; import {translate} from "react-i18next";
import { import {
createPermission,
createPermissionReset,
deletePermissionReset,
fetchPermissions, fetchPermissions,
getCreatePermissionFailure,
getDeletePermissionsFailure,
getFetchPermissionsFailure, getFetchPermissionsFailure,
isFetchPermissionsPending, getModifyPermissionsFailure,
getPermissionsOfRepo, getPermissionsOfRepo,
hasCreatePermission, hasCreatePermission,
createPermission,
isCreatePermissionPending, isCreatePermissionPending,
getCreatePermissionFailure, isFetchPermissionsPending,
createPermissionReset, modifyPermissionReset
getDeletePermissionsFailure,
getModifyPermissionsFailure,
modifyPermissionReset,
deletePermissionReset
} from "../modules/permissions"; } from "../modules/permissions";
import { Loading, ErrorPage } from "@scm-manager/ui-components"; import {ErrorPage, Loading} from "@scm-manager/ui-components";
import type { import type {Permission, PermissionCollection, PermissionCreateEntry} from "@scm-manager/ui-types";
Permission,
PermissionCollection,
PermissionEntry
} from "@scm-manager/ui-types";
import SinglePermission from "./SinglePermission"; import SinglePermission from "./SinglePermission";
import CreatePermissionForm from "../components/CreatePermissionForm"; import CreatePermissionForm from "../components/CreatePermissionForm";
import type { History } from "history"; import type {History} from "history";
type Props = { type Props = {
namespace: string, namespace: string,
@@ -39,7 +35,7 @@ type Props = {
//dispatch functions //dispatch functions
fetchPermissions: (namespace: string, repoName: string) => void, fetchPermissions: (namespace: string, repoName: string) => void,
createPermission: ( createPermission: (
permission: PermissionEntry, permission: PermissionCreateEntry,
namespace: string, namespace: string,
repoName: string, repoName: string,
callback?: () => void callback?: () => void
@@ -176,7 +172,7 @@ const mapDispatchToProps = dispatch => {
dispatch(fetchPermissions(namespace, repoName)); dispatch(fetchPermissions(namespace, repoName));
}, },
createPermission: ( createPermission: (
permission: PermissionEntry, permission: PermissionCreateEntry,
namespace: string, namespace: string,
repoName: string, repoName: string,
callback?: () => void callback?: () => void

View File

@@ -1,16 +1,12 @@
// @flow // @flow
import { apiClient } from "@scm-manager/ui-components"; import type {Action} from "@scm-manager/ui-components";
import {apiClient} from "@scm-manager/ui-components";
import * as types from "../../../modules/types"; import * as types from "../../../modules/types";
import type { Action } from "@scm-manager/ui-components"; import type {Permission, PermissionCollection, PermissionCreateEntry} from "@scm-manager/ui-types";
import type { import {isPending} from "../../../modules/pending";
PermissionCollection, import {getFailure} from "../../../modules/failure";
Permission, import {Dispatch} from "redux";
PermissionEntry
} from "@scm-manager/ui-types";
import { isPending } from "../../../modules/pending";
import { getFailure } from "../../../modules/failure";
import { Dispatch } from "redux";
export const FETCH_PERMISSIONS = "scm/permissions/FETCH_PERMISSIONS"; export const FETCH_PERMISSIONS = "scm/permissions/FETCH_PERMISSIONS";
export const FETCH_PERMISSIONS_PENDING = `${FETCH_PERMISSIONS}_${ export const FETCH_PERMISSIONS_PENDING = `${FETCH_PERMISSIONS}_${
@@ -219,7 +215,7 @@ export function modifyPermissionReset(namespace: string, repoName: string) {
// create permission // create permission
export function createPermission( export function createPermission(
permission: PermissionEntry, permission: PermissionCreateEntry,
namespace: string, namespace: string,
repoName: string, repoName: string,
callback?: () => void callback?: () => void
@@ -260,7 +256,7 @@ export function createPermission(
} }
export function createPermissionPending( export function createPermissionPending(
permission: PermissionEntry, permission: PermissionCreateEntry,
namespace: string, namespace: string,
repoName: string repoName: string
): Action { ): Action {
@@ -272,7 +268,7 @@ export function createPermissionPending(
} }
export function createPermissionSuccess( export function createPermissionSuccess(
permission: PermissionEntry, permission: PermissionCreateEntry,
namespace: string, namespace: string,
repoName: string repoName: string
): Action { ): Action {

View File

@@ -3,44 +3,44 @@ import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk"; import thunk from "redux-thunk";
import fetchMock from "fetch-mock"; import fetchMock from "fetch-mock";
import reducer, { import reducer, {
fetchPermissions, CREATE_PERMISSION,
fetchPermissionsSuccess, CREATE_PERMISSION_FAILURE,
getPermissionsOfRepo,
isFetchPermissionsPending,
getFetchPermissionsFailure,
modifyPermission,
modifyPermissionSuccess,
getModifyPermissionFailure,
isModifyPermissionPending,
createPermission,
hasCreatePermission,
deletePermission,
deletePermissionSuccess,
getDeletePermissionFailure,
isDeletePermissionPending,
getModifyPermissionsFailure,
MODIFY_PERMISSION_FAILURE,
MODIFY_PERMISSION_PENDING,
FETCH_PERMISSIONS,
FETCH_PERMISSIONS_PENDING,
FETCH_PERMISSIONS_SUCCESS,
FETCH_PERMISSIONS_FAILURE,
MODIFY_PERMISSION_SUCCESS,
MODIFY_PERMISSION,
CREATE_PERMISSION_PENDING, CREATE_PERMISSION_PENDING,
CREATE_PERMISSION_SUCCESS, CREATE_PERMISSION_SUCCESS,
CREATE_PERMISSION_FAILURE, createPermission,
createPermissionSuccess,
DELETE_PERMISSION, DELETE_PERMISSION,
DELETE_PERMISSION_FAILURE,
DELETE_PERMISSION_PENDING, DELETE_PERMISSION_PENDING,
DELETE_PERMISSION_SUCCESS, DELETE_PERMISSION_SUCCESS,
DELETE_PERMISSION_FAILURE, deletePermission,
CREATE_PERMISSION, deletePermissionSuccess,
createPermissionSuccess, FETCH_PERMISSIONS,
FETCH_PERMISSIONS_FAILURE,
FETCH_PERMISSIONS_PENDING,
FETCH_PERMISSIONS_SUCCESS,
fetchPermissions,
fetchPermissionsSuccess,
getCreatePermissionFailure, getCreatePermissionFailure,
getDeletePermissionFailure,
getDeletePermissionsFailure,
getFetchPermissionsFailure,
getModifyPermissionFailure,
getModifyPermissionsFailure,
getPermissionsOfRepo,
hasCreatePermission,
isCreatePermissionPending, isCreatePermissionPending,
getDeletePermissionsFailure isDeletePermissionPending,
isFetchPermissionsPending,
isModifyPermissionPending,
MODIFY_PERMISSION,
MODIFY_PERMISSION_FAILURE,
MODIFY_PERMISSION_PENDING,
MODIFY_PERMISSION_SUCCESS,
modifyPermission,
modifyPermissionSuccess
} from "./permissions"; } from "./permissions";
import type { Permission, PermissionCollection } from "@scm-manager/ui-types"; import type {Permission, PermissionCollection} from "@scm-manager/ui-types";
const hitchhiker_puzzle42Permission_user_eins: Permission = { const hitchhiker_puzzle42Permission_user_eins: Permission = {
name: "user_eins", name: "user_eins",
@@ -640,7 +640,7 @@ describe("permissions selectors", () => {
it("should return true, when createPermission is true", () => { it("should return true, when createPermission is true", () => {
const state = { const state = {
permissions: { permissions: {
["hitchhiker/puzzle42"]: { "hitchhiker/puzzle42": {
createPermission: true createPermission: true
} }
} }
@@ -651,7 +651,7 @@ describe("permissions selectors", () => {
it("should return false, when createPermission is false", () => { it("should return false, when createPermission is false", () => {
const state = { const state = {
permissions: { permissions: {
["hitchhiker/puzzle42"]: { "hitchhiker/puzzle42": {
createPermission: false createPermission: false
} }
} }

View File

@@ -5,7 +5,6 @@ import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import sonia.scm.NotFoundException; import sonia.scm.NotFoundException;
import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.RevisionNotFoundException;
import sonia.scm.repository.api.DiffFormat;
import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.util.HttpUtil; import sonia.scm.util.HttpUtil;
@@ -58,7 +57,7 @@ public class DiffRootResource {
try { try {
repositoryService.getDiffCommand() repositoryService.getDiffCommand()
.setRevision(revision) .setRevision(revision)
.setFormat(DiffFormat.GIT) // TODO: Configure this at request time. Maybe as a query param? // .setFormat(DiffFormat.GIT) // TODO: Configure this at request time. Maybe as a query param?
.retriveContent(output); .retriveContent(output);
} catch (RevisionNotFoundException e) { } catch (RevisionNotFoundException e) {
throw new WebApplicationException(Response.Status.NOT_FOUND); throw new WebApplicationException(Response.Status.NOT_FOUND);