Merged 2.0.0-m3

This commit is contained in:
Philipp Czora
2019-01-23 17:52:59 +01:00
28 changed files with 111 additions and 56 deletions

View File

@@ -386,7 +386,7 @@
<plugin> <plugin>
<groupId>com.github.sdorra</groupId> <groupId>com.github.sdorra</groupId>
<artifactId>buildfrontend-maven-plugin</artifactId> <artifactId>buildfrontend-maven-plugin</artifactId>
<version>2.1.1</version> <version>2.2.0</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
@@ -817,6 +817,10 @@
<!-- *UserPassword JS files are excluded because extraction of common code would not make the code more readable --> <!-- *UserPassword JS files are excluded because extraction of common code would not make the code more readable -->
<sonar.cpd.exclusions>**/*StoreFactory.java,**/*UserPassword.js</sonar.cpd.exclusions> <sonar.cpd.exclusions>**/*StoreFactory.java,**/*UserPassword.js</sonar.cpd.exclusions>
<node.version>8.11.4</node.version>
<sonar.nodejs.executable>./scm-ui/target/frontend/buildfrontend-node/node-v${node.version}-linux-x64/bin/node</sonar.nodejs.executable>
</properties> </properties>
</project> </project>

View File

@@ -25,13 +25,13 @@ class LinkPaginator extends React.Component<Props> {
); );
} }
renderPreviousButton(label?: string) { renderPreviousButton(className: string, label?: string) {
const { page } = this.props; const { page } = this.props;
const previousPage = page - 1; const previousPage = page - 1;
return ( return (
<Button <Button
className={"pagination-previous"} className={className}
label={label ? label : previousPage.toString()} label={label ? label : previousPage.toString()}
disabled={!this.hasLink("prev")} disabled={!this.hasLink("prev")}
link={`${previousPage}`} link={`${previousPage}`}
@@ -44,12 +44,12 @@ class LinkPaginator extends React.Component<Props> {
return collection._links[name]; return collection._links[name];
} }
renderNextButton(label?: string) { renderNextButton(className: string, label?: string) {
const { page } = this.props; const { page } = this.props;
const nextPage = page + 1; const nextPage = page + 1;
return ( return (
<Button <Button
className={"pagination-next"} className={className}
label={label ? label : nextPage.toString()} label={label ? label : nextPage.toString()}
disabled={!this.hasLink("next")} disabled={!this.hasLink("next")}
link={`${nextPage}`} link={`${nextPage}`}
@@ -96,13 +96,13 @@ class LinkPaginator extends React.Component<Props> {
links.push(this.separator()); links.push(this.separator());
} }
if (page > 2) { if (page > 2) {
links.push(this.renderPreviousButton()); links.push(this.renderPreviousButton("pagination-link"));
} }
links.push(this.currentPage(page)); links.push(this.currentPage(page));
if (page + 1 < pageTotal) { if (page + 1 < pageTotal) {
links.push(this.renderNextButton()); links.push(this.renderNextButton("pagination-link"));
} }
if (page + 2 < pageTotal) if (page + 2 < pageTotal)
//if there exists pages between next and last //if there exists pages between next and last
@@ -118,13 +118,13 @@ class LinkPaginator extends React.Component<Props> {
const { t } = this.props; const { t } = this.props;
return ( return (
<nav className="pagination is-centered" aria-label="pagination"> <nav className="pagination is-centered" aria-label="pagination">
{this.renderPreviousButton(t("paginator.previous"))} {this.renderPreviousButton("pagination-previous", t("paginator.previous"))}
<ul className="pagination-list"> <ul className="pagination-list">
{this.pageLinks().map((link, index) => { {this.pageLinks().map((link, index) => {
return <li key={index}>{link}</li>; return <li key={index}>{link}</li>;
})} })}
</ul> </ul>
{this.renderNextButton(t("paginator.next"))} {this.renderNextButton("pagination-next", t("paginator.next"))}
</nav> </nav>
); );
} }

View File

@@ -1,10 +1,7 @@
//@flow //@flow
import React from "react"; import React from "react";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import { import RemoveEntryOfTableButton from "../buttons/RemoveEntryOfTableButton";
RemoveEntryOfTableButton,
LabelWithHelpIcon
} from "@scm-manager/ui-components";
type Props = { type Props = {
members: string[], members: string[],
@@ -19,10 +16,6 @@ class MemberNameTable extends React.Component<Props, State> {
const { t } = this.props; const { t } = this.props;
return ( return (
<div> <div>
<LabelWithHelpIcon
label={t("group.members")}
helpText={t("group-form.help.memberHelpText")}
/>
<table className="table is-hoverable is-fullwidth"> <table className="table is-hoverable is-fullwidth">
<tbody> <tbody>
{this.props.members.map(member => { {this.props.members.map(member => {

View File

@@ -2,6 +2,7 @@
export { default as AddEntryToTableField } from "./AddEntryToTableField.js"; export { default as AddEntryToTableField } from "./AddEntryToTableField.js";
export { default as AutocompleteAddEntryToTableField } from "./AutocompleteAddEntryToTableField.js"; export { default as AutocompleteAddEntryToTableField } from "./AutocompleteAddEntryToTableField.js";
export { default as MemberNameTable } from "./MemberNameTable.js";
export { default as Checkbox } from "./Checkbox.js"; export { default as Checkbox } from "./Checkbox.js";
export { default as InputField } from "./InputField.js"; export { default as InputField } from "./InputField.js";
export { default as Select } from "./Select.js"; export { default as Select } from "./Select.js";

View File

@@ -2,16 +2,23 @@
import React from "react"; import React from "react";
type Props = { type Props = {
icon?: string,
label: string, label: string,
action: () => void action: () => void
}; };
class NavAction extends React.Component<Props> { class NavAction extends React.Component<Props> {
render() { render() {
const { label, action } = this.props; const { label, icon, action } = this.props;
let showIcon = null;
if (icon) {
showIcon = (<><i className={icon}></i>{" "}</>);
}
return ( return (
<li> <li>
<a onClick={action} href="javascript:void(0);">{label}</a> <a onClick={action} href="javascript:void(0);">{showIcon}{label}</a>
</li> </li>
); );
} }

View File

@@ -6,6 +6,7 @@ import {Link, Route} from "react-router-dom";
type Props = { type Props = {
to: string, to: string,
icon?: string,
label: string, label: string,
activeOnlyWhenExact?: boolean, activeOnlyWhenExact?: boolean,
activeWhenMatch?: (route: any) => boolean activeWhenMatch?: (route: any) => boolean
@@ -23,10 +24,17 @@ class NavLink extends React.Component<Props> {
} }
renderLink = (route: any) => { renderLink = (route: any) => {
const { to, label } = this.props; const { to, icon, label } = this.props;
let showIcon = null;
if (icon) {
showIcon = (<><i className={icon}></i>{" "}</>);
}
return ( return (
<li> <li>
<Link className={this.isActive(route) ? "is-active" : ""} to={to}> <Link className={this.isActive(route) ? "is-active" : ""} to={to}>
{showIcon}
{label} {label}
</Link> </Link>
</li> </li>
@@ -35,6 +43,7 @@ class NavLink extends React.Component<Props> {
render() { render() {
const { to, activeOnlyWhenExact } = this.props; const { to, activeOnlyWhenExact } = this.props;
return ( return (
<Route path={to} exact={activeOnlyWhenExact} children={this.renderLink} /> <Route path={to} exact={activeOnlyWhenExact} children={this.renderLink} />
); );

View File

@@ -70,7 +70,7 @@ class Profile extends React.Component<Props, State> {
<div className="column"> <div className="column">
<Navigation> <Navigation>
<Section label={t("profile.navigation-label")}> <Section label={t("profile.navigation-label")}>
<NavLink to={`${url}`} label={t("profile.information")} /> <NavLink to={`${url}`} icon="fas fa-info-circle" label={t("profile.information")} />
</Section> </Section>
<Section label={t("profile.actions-label")}> <Section label={t("profile.actions-label")}>
<NavLink <NavLink

View File

@@ -3,6 +3,8 @@ import React from "react";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import { import {
AutocompleteAddEntryToTableField, AutocompleteAddEntryToTableField,
LabelWithHelpIcon,
MemberNameTable,
InputField, InputField,
SubmitButton, SubmitButton,
Textarea Textarea
@@ -10,7 +12,6 @@ import {
import type { Group, SelectValue } from "@scm-manager/ui-types"; import type { Group, SelectValue } from "@scm-manager/ui-types";
import * as validator from "./groupValidation"; import * as validator from "./groupValidation";
import MemberNameTable from "./MemberNameTable";
type Props = { type Props = {
t: string => string, t: string => string,
@@ -97,6 +98,10 @@ class GroupForm extends React.Component<Props, State> {
validationError={false} validationError={false}
helpText={t("group-form.help.descriptionHelpText")} helpText={t("group-form.help.descriptionHelpText")}
/> />
<LabelWithHelpIcon
label={t("group.members")}
helpText={t("group-form.help.memberHelpText")}
/>
<MemberNameTable <MemberNameTable
members={group.members} members={group.members}
memberListChanged={this.memberListChanged} memberListChanged={this.memberListChanged}

View File

@@ -49,7 +49,7 @@ export class DeleteGroupNavLink extends React.Component<Props> {
if (!this.isDeletable()) { if (!this.isDeletable()) {
return null; return null;
} }
return <NavAction label={t("delete-group-button.label")} action={action} />; return <NavAction icon="fas fa-times" label={t("delete-group-button.label")} action={action} />;
} }
} }

View File

@@ -18,7 +18,7 @@ class EditGroupNavLink extends React.Component<Props, State> {
if (!this.isEditable()) { if (!this.isEditable()) {
return null; return null;
} }
return <NavLink label={t("edit-group-button.label")} to={editUrl} />; return <NavLink to={editUrl} icon="fas fa-cog" label={t("edit-group-button.label")} />;
} }
isEditable = () => { isEditable = () => {

View File

@@ -17,7 +17,7 @@ class ChangePermissionNavLink extends React.Component<Props> {
if (!this.hasPermissionToSetPermission()) { if (!this.hasPermissionToSetPermission()) {
return null; return null;
} }
return <NavLink label={t("set-permissions-button.label")} to={permissionsUrl} />; return <NavLink to={permissionsUrl} label={t("set-permissions-button.label")} />;
} }
hasPermissionToSetPermission = () => { hasPermissionToSetPermission = () => {

View File

@@ -68,11 +68,13 @@ class AddGroup extends React.Component<Props, State> {
}); });
}); });
}; };
groupCreated = () => { groupCreated = (group: Group) => {
this.props.history.push("/groups"); this.props.history.push("/group/" + group.name);
}; };
createGroup = (group: Group) => { createGroup = (group: Group) => {
this.props.createGroup(this.props.createLink, group, this.groupCreated); this.props.createGroup(this.props.createLink, group, () =>
this.groupCreated(group)
);
}; };
} }

View File

@@ -131,6 +131,7 @@ class SingleGroup extends React.Component<Props> {
<Section label={t("single-group.navigation-label")}> <Section label={t("single-group.navigation-label")}>
<NavLink <NavLink
to={`${url}`} to={`${url}`}
icon="fas fa-info-circle"
label={t("single-group.information-label")} label={t("single-group.information-label")}
/> />
<SetPermissionsNavLink <SetPermissionsNavLink
@@ -149,7 +150,11 @@ class SingleGroup extends React.Component<Props> {
deleteGroup={this.deleteGroup} deleteGroup={this.deleteGroup}
/> />
<EditGroupNavLink group={group} editUrl={`${url}/edit`} /> <EditGroupNavLink group={group} editUrl={`${url}/edit`} />
<NavLink to="/groups" label={t("single-group.back-label")} /> <NavLink
to="/groups"
icon="fas fa-undo-alt"
label={t("single-group.back-label")}
/>
</Section> </Section>
</Navigation> </Navigation>
</div> </div>

View File

@@ -51,7 +51,7 @@ class DeleteNavAction extends React.Component<Props> {
if (!this.isDeletable()) { if (!this.isDeletable()) {
return null; return null;
} }
return <NavAction label={t("delete-nav-action.label")} action={action} />; return <NavAction action={action} icon="fas fa-times" label={t("delete-nav-action.label")} />;
} }
} }

View File

@@ -15,7 +15,7 @@ class EditNavLink extends React.Component<Props> {
return null; return null;
} }
const { editUrl, t } = this.props; const { editUrl, t } = this.props;
return <NavLink to={editUrl} label={t("edit-nav-link.label")} />; return <NavLink to={editUrl} icon="fas fa-cog" label={t("edit-nav-link.label")} />;
} }
} }

View File

@@ -33,6 +33,6 @@ describe("EditNavLink", () => {
<EditNavLink repository={repository} editUrl="" />, <EditNavLink repository={repository} editUrl="" />,
options.get() options.get()
); );
expect(navLink.text()).toBe("edit-nav-link.label"); expect(navLink.text()).toBe(" edit-nav-link.label");
}); });
}); });

View File

@@ -20,7 +20,7 @@ class PermissionsNavLink extends React.Component<Props> {
} }
const { permissionUrl, t } = this.props; const { permissionUrl, t } = this.props;
return ( return (
<NavLink to={permissionUrl} label={t("repository-root.permissions")} /> <NavLink to={permissionUrl} icon="fas fa-lock" label={t("repository-root.permissions")} />
); );
} }
} }

View File

@@ -33,6 +33,6 @@ describe("PermissionsNavLink", () => {
<PermissionsNavLink repository={repository} permissionUrl="" />, <PermissionsNavLink repository={repository} permissionUrl="" />,
options.get() options.get()
); );
expect(navLink.text()).toBe("repository-root.permissions"); expect(navLink.text()).toBe(" repository-root.permissions");
}); });
}); });

View File

@@ -29,7 +29,11 @@ type Props = {
// dispatch functions // dispatch functions
fetchRepositoryTypesIfNeeded: () => void, fetchRepositoryTypesIfNeeded: () => void,
createRepo: (link: string, Repository, callback: () => void) => void, createRepo: (
link: string,
Repository,
callback: (repo: Repository) => void
) => void,
resetForm: () => void, resetForm: () => void,
// context props // context props
@@ -43,9 +47,10 @@ class Create extends React.Component<Props> {
this.props.fetchRepositoryTypesIfNeeded(); this.props.fetchRepositoryTypesIfNeeded();
} }
repoCreated = () => { repoCreated = (repo: Repository) => {
const { history } = this.props; const { history } = this.props;
history.push("/repos");
history.push("/repo/" + repo.namespace + "/" + repo.name);
}; };
render() { render() {
@@ -70,7 +75,9 @@ class Create extends React.Component<Props> {
repositoryTypes={repositoryTypes} repositoryTypes={repositoryTypes}
loading={createLoading} loading={createLoading}
submitForm={repo => { submitForm={repo => {
createRepo(repoLink, repo, this.repoCreated); createRepo(repoLink, repo, (repo: Repository) =>
this.repoCreated(repo)
);
}} }}
/> />
</Page> </Page>

View File

@@ -169,11 +169,12 @@ class RepositoryRoot extends React.Component<Props> {
<div className="column"> <div className="column">
<Navigation> <Navigation>
<Section label={t("repository-root.navigation-label")}> <Section label={t("repository-root.navigation-label")}>
<NavLink to={url} label={t("repository-root.information")} /> <NavLink to={url} icon="fas fa-info-circle" label={t("repository-root.information")} />
<RepositoryNavLink <RepositoryNavLink
repository={repository} repository={repository}
linkName="changesets" linkName="changesets"
to={`${url}/changesets/`} to={`${url}/changesets/`}
icon="fas fa-code-branch"
label={t("repository-root.history")} label={t("repository-root.history")}
activeWhenMatch={this.matches} activeWhenMatch={this.matches}
activeOnlyWhenExact={false} activeOnlyWhenExact={false}
@@ -182,6 +183,7 @@ class RepositoryRoot extends React.Component<Props> {
repository={repository} repository={repository}
linkName="sources" linkName="sources"
to={`${url}/sources`} to={`${url}/sources`}
icon="fas fa-code"
label={t("repository-root.sources")} label={t("repository-root.sources")}
activeOnlyWhenExact={false} activeOnlyWhenExact={false}
/> />
@@ -189,7 +191,6 @@ class RepositoryRoot extends React.Component<Props> {
permissionUrl={`${url}/permissions`} permissionUrl={`${url}/permissions`}
repository={repository} repository={repository}
/> />
<EditNavLink repository={repository} editUrl={`${url}/edit`} />
<ExtensionPoint <ExtensionPoint
name="repository.navigation" name="repository.navigation"
props={extensionProps} props={extensionProps}
@@ -198,7 +199,8 @@ class RepositoryRoot extends React.Component<Props> {
</Section> </Section>
<Section label={t("repository-root.actions-label")}> <Section label={t("repository-root.actions-label")}>
<DeleteNavAction repository={repository} delete={this.delete} /> <DeleteNavAction repository={repository} delete={this.delete} />
<NavLink to="/repos" label={t("repository-root.back-label")} /> <EditNavLink repository={repository} editUrl={`${url}/edit`} />
<NavLink to="/repos" icon="fas fa-undo" label={t("repository-root.back-label")} />
</Section> </Section>
</Navigation> </Navigation>
</div> </div>

View File

@@ -164,16 +164,21 @@ export function fetchRepoFailure(
export function createRepo( export function createRepo(
link: string, link: string,
repository: Repository, repository: Repository,
callback?: () => void callback?: (repo: Repository) => void
) { ) {
return function(dispatch: any) { return function(dispatch: any) {
dispatch(createRepoPending()); dispatch(createRepoPending());
return apiClient return apiClient
.post(link, repository, CONTENT_TYPE) .post(link, repository, CONTENT_TYPE)
.then(() => { .then(response => {
const location = response.headers.get("Location");
dispatch(createRepoSuccess()); dispatch(createRepoSuccess());
return apiClient.get(location);
})
.then(response => response.json())
.then(response => {
if (callback) { if (callback) {
callback(); callback(response);
} }
}) })
.catch(err => { .catch(err => {

View File

@@ -415,9 +415,14 @@ describe("repos fetch", () => {
it("should successfully create repo slarti/fjords", () => { it("should successfully create repo slarti/fjords", () => {
fetchMock.postOnce(REPOS_URL, { fetchMock.postOnce(REPOS_URL, {
status: 201 status: 201,
headers: {
location: "repositories/slarti/fjords"
}
}); });
fetchMock.getOnce(REPOS_URL + "/slarti/fjords", slartiFjords);
const expectedActions = [ const expectedActions = [
{ {
type: CREATE_REPO_PENDING type: CREATE_REPO_PENDING
@@ -435,12 +440,19 @@ describe("repos fetch", () => {
it("should successfully create repo slarti/fjords and call the callback", () => { it("should successfully create repo slarti/fjords and call the callback", () => {
fetchMock.postOnce(REPOS_URL, { fetchMock.postOnce(REPOS_URL, {
status: 201 status: 201,
headers: {
location: "repositories/slarti/fjords"
}
}); });
fetchMock.getOnce(REPOS_URL + "/slarti/fjords", slartiFjords);
let callMe = "not yet"; let callMe = "not yet";
const callback = () => { const callback = (r: any) => {
expect(r).toEqual(slartiFjords);
callMe = "yeah"; callMe = "yeah";
}; };

View File

@@ -49,7 +49,7 @@ class DeleteUserNavLink extends React.Component<Props> {
if (!this.isDeletable()) { if (!this.isDeletable()) {
return null; return null;
} }
return <NavAction label={t("delete-user-button.label")} action={action} />; return <NavAction icon="fas fa-times" label={t("delete-user-button.label")} action={action} />;
} }
} }

View File

@@ -17,7 +17,7 @@ class EditUserNavLink extends React.Component<Props> {
if (!this.isEditable()) { if (!this.isEditable()) {
return null; return null;
} }
return <NavLink label={t("edit-user-button.label")} to={editUrl} />; return <NavLink to={editUrl} icon="fas fa-cog" label={t("edit-user-button.label")} />;
} }
isEditable = () => { isEditable = () => {

View File

@@ -17,7 +17,7 @@ class ChangePasswordNavLink extends React.Component<Props> {
if (!this.hasPermissionToSetPassword()) { if (!this.hasPermissionToSetPassword()) {
return null; return null;
} }
return <NavLink label={t("set-password-button.label")} to={passwordUrl} />; return <NavLink to={passwordUrl} label={t("set-password-button.label")} />;
} }
hasPermissionToSetPassword = () => { hasPermissionToSetPassword = () => {

View File

@@ -17,7 +17,7 @@ class ChangePermissionNavLink extends React.Component<Props> {
if (!this.hasPermissionToSetPermission()) { if (!this.hasPermissionToSetPermission()) {
return null; return null;
} }
return <NavLink label={t("set-permissions-button.label")} to={permissionsUrl} />; return <NavLink to={permissionsUrl} label={t("set-permissions-button.label")} />;
} }
hasPermissionToSetPermission = () => { hasPermissionToSetPermission = () => {

View File

@@ -12,7 +12,7 @@ import {
} from "../modules/users"; } from "../modules/users";
import { Page } from "@scm-manager/ui-components"; import { Page } from "@scm-manager/ui-components";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import {getUsersLink} from "../../modules/indexResource"; import { getUsersLink } from "../../modules/indexResource";
type Props = { type Props = {
loading?: boolean, loading?: boolean,
@@ -33,13 +33,15 @@ class AddUser extends React.Component<Props> {
this.props.resetForm(); this.props.resetForm();
} }
userCreated = () => { userCreated = (user: User) => {
const { history } = this.props; const { history } = this.props;
history.push("/users"); history.push("/user/" + user.name);
}; };
createUser = (user: User) => { createUser = (user: User) => {
this.props.addUser(this.props.usersLink, user, this.userCreated); this.props.addUser(this.props.usersLink, user, () =>
this.userCreated(user)
);
}; };
render() { render() {

View File

@@ -122,6 +122,7 @@ class SingleUser extends React.Component<Props> {
<Section label={t("single-user.navigation-label")}> <Section label={t("single-user.navigation-label")}>
<NavLink <NavLink
to={`${url}`} to={`${url}`}
icon="fas fa-info-circle"
label={t("single-user.information-label")} label={t("single-user.information-label")}
/> />
<EditUserNavLink user={user} editUrl={`${url}/edit`} /> <EditUserNavLink user={user} editUrl={`${url}/edit`} />
@@ -136,7 +137,7 @@ class SingleUser extends React.Component<Props> {
</Section> </Section>
<Section label={t("single-user.actions-label")}> <Section label={t("single-user.actions-label")}>
<DeleteUserNavLink user={user} deleteUser={this.deleteUser} /> <DeleteUserNavLink user={user} deleteUser={this.deleteUser} />
<NavLink to="/users" label={t("single-user.back-label")} /> <NavLink to="/users" icon="fas fa-undo" label={t("single-user.back-label")} />
</Section> </Section>
</Navigation> </Navigation>
</div> </div>