mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-16 02:06:18 +01:00
Merge with 2.0.0-m3
This commit is contained in:
@@ -32,9 +32,8 @@ export function fetchConfig(link: string) {
|
||||
.then(data => {
|
||||
dispatch(fetchConfigSuccess(data));
|
||||
})
|
||||
.catch(cause => {
|
||||
const error = new Error(`could not fetch config: ${cause.message}`);
|
||||
dispatch(fetchConfigFailure(error));
|
||||
.catch(err => {
|
||||
dispatch(fetchConfigFailure(err));
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -73,13 +72,8 @@ export function modifyConfig(config: Config, callback?: () => void) {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
.catch(cause => {
|
||||
dispatch(
|
||||
modifyConfigFailure(
|
||||
config,
|
||||
new Error(`could not modify config: ${cause.message}`)
|
||||
)
|
||||
);
|
||||
.catch(err => {
|
||||
dispatch(modifyConfigFailure(config, err));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ type State = {
|
||||
password: string,
|
||||
loading: boolean,
|
||||
error?: Error,
|
||||
passwordChanged: boolean
|
||||
passwordChanged: boolean,
|
||||
passwordValid: boolean
|
||||
};
|
||||
|
||||
class ChangeUserPassword extends React.Component<Props, State> {
|
||||
@@ -35,7 +36,8 @@ class ChangeUserPassword extends React.Component<Props, State> {
|
||||
passwordConfirmationError: false,
|
||||
validatePasswordError: false,
|
||||
validatePassword: "",
|
||||
passwordChanged: false
|
||||
passwordChanged: false,
|
||||
passwordValid: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -83,6 +85,10 @@ class ChangeUserPassword extends React.Component<Props, State> {
|
||||
}
|
||||
};
|
||||
|
||||
isValid = () => {
|
||||
return this.state.oldPassword && this.state.passwordValid;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
const { loading, passwordChanged, error } = this.state;
|
||||
@@ -118,7 +124,7 @@ class ChangeUserPassword extends React.Component<Props, State> {
|
||||
key={this.state.passwordChanged ? "changed" : "unchanged"}
|
||||
/>
|
||||
<SubmitButton
|
||||
disabled={!this.state.password}
|
||||
disabled={!this.isValid()}
|
||||
loading={loading}
|
||||
label={t("password.submit")}
|
||||
/>
|
||||
@@ -126,8 +132,8 @@ class ChangeUserPassword extends React.Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
passwordChanged = (password: string) => {
|
||||
this.setState({ ...this.state, password });
|
||||
passwordChanged = (password: string, passwordValid: boolean) => {
|
||||
this.setState({ ...this.state, password, passwordValid: (!!password && passwordValid) });
|
||||
};
|
||||
|
||||
onClose = () => {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import AvatarWrapper from "../repos/components/changesets/AvatarWrapper";
|
||||
import type { Me } from "@scm-manager/ui-types";
|
||||
import { MailLink } from "@scm-manager/ui-components";
|
||||
import { MailLink, AvatarWrapper, AvatarImage } from "@scm-manager/ui-components";
|
||||
import { compose } from "redux";
|
||||
import { translate } from "react-i18next";
|
||||
|
||||
@@ -18,37 +17,35 @@ class ProfileInfo extends React.Component<Props, State> {
|
||||
render() {
|
||||
const { me, t } = this.props;
|
||||
return (
|
||||
<>
|
||||
<div className="media">
|
||||
<AvatarWrapper>
|
||||
<div>
|
||||
<figure className="media-left">
|
||||
<p className="image is-64x64">
|
||||
{
|
||||
// TODO: add avatar
|
||||
}
|
||||
</p>
|
||||
</figure>
|
||||
</div>
|
||||
<figure className="media-left">
|
||||
<p className="image is-64x64">
|
||||
<AvatarImage person={ me }/>
|
||||
</p>
|
||||
</figure>
|
||||
</AvatarWrapper>
|
||||
<table className="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("profile.username")}</td>
|
||||
<td>{me.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("profile.displayName")}</td>
|
||||
<td>{me.displayName}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("profile.mail")}</td>
|
||||
<td>
|
||||
<MailLink address={me.mail} />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
<div className="media-content">
|
||||
<table className="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("profile.username")}</td>
|
||||
<td>{me.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("profile.displayName")}</td>
|
||||
<td>{me.displayName}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="has-text-weight-semibold">{t("profile.mail")}</td>
|
||||
<td>
|
||||
<MailLink address={me.mail} />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import {
|
||||
AutocompleteAddEntryToTableField,
|
||||
InputField,
|
||||
SubmitButton,
|
||||
Textarea,
|
||||
AddEntryToTableField
|
||||
Textarea
|
||||
} from "@scm-manager/ui-components";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
import type { Group, SelectValue } from "@scm-manager/ui-types";
|
||||
|
||||
import * as validator from "./groupValidation";
|
||||
import MemberNameTable from "./MemberNameTable";
|
||||
@@ -16,7 +16,8 @@ type Props = {
|
||||
t: string => string,
|
||||
submitForm: Group => void,
|
||||
loading?: boolean,
|
||||
group?: Group
|
||||
group?: Group,
|
||||
loadUserSuggestions: string => any
|
||||
};
|
||||
|
||||
type State = {
|
||||
@@ -70,7 +71,7 @@ class GroupForm extends React.Component<Props, State> {
|
||||
|
||||
render() {
|
||||
const { t, loading } = this.props;
|
||||
const group = this.state.group;
|
||||
const { group } = this.state;
|
||||
let nameField = null;
|
||||
if (!this.props.group) {
|
||||
nameField = (
|
||||
@@ -97,15 +98,20 @@ class GroupForm extends React.Component<Props, State> {
|
||||
helpText={t("group-form.help.descriptionHelpText")}
|
||||
/>
|
||||
<MemberNameTable
|
||||
members={this.state.group.members}
|
||||
members={group.members}
|
||||
memberListChanged={this.memberListChanged}
|
||||
/>
|
||||
<AddEntryToTableField
|
||||
|
||||
<AutocompleteAddEntryToTableField
|
||||
addEntry={this.addMember}
|
||||
disabled={false}
|
||||
buttonLabel={t("add-member-button.label")}
|
||||
fieldLabel={t("add-member-textfield.label")}
|
||||
errorMessage={t("add-member-textfield.error")}
|
||||
loadSuggestions={this.props.loadUserSuggestions}
|
||||
placeholder={t("add-member-autocomplete.placeholder")}
|
||||
loadingMessage={t("add-member-autocomplete.loading")}
|
||||
noOptionsMessage={t("add-member-autocomplete.no-options")}
|
||||
/>
|
||||
<SubmitButton
|
||||
disabled={!this.isValid()}
|
||||
@@ -126,8 +132,8 @@ class GroupForm extends React.Component<Props, State> {
|
||||
});
|
||||
};
|
||||
|
||||
addMember = (membername: string) => {
|
||||
if (this.isMember(membername)) {
|
||||
addMember = (value: SelectValue) => {
|
||||
if (this.isMember(value.value.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -135,7 +141,7 @@ class GroupForm extends React.Component<Props, State> {
|
||||
...this.state,
|
||||
group: {
|
||||
...this.state.group,
|
||||
members: [...this.state.group.members, membername]
|
||||
members: [...this.state.group.members, value.value.id]
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -13,7 +13,10 @@ import {
|
||||
} from "../modules/groups";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
import type { History } from "history";
|
||||
import { getGroupsLink } from "../../modules/indexResource";
|
||||
import {
|
||||
getGroupsLink,
|
||||
getUserAutoCompleteLink
|
||||
} from "../../modules/indexResource";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
@@ -22,7 +25,8 @@ type Props = {
|
||||
loading?: boolean,
|
||||
error?: Error,
|
||||
resetForm: () => void,
|
||||
createLink: string
|
||||
createLink: string,
|
||||
autocompleteLink: string
|
||||
};
|
||||
|
||||
type State = {};
|
||||
@@ -31,6 +35,7 @@ class AddGroup extends React.Component<Props, State> {
|
||||
componentDidMount() {
|
||||
this.props.resetForm();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { t, loading, error } = this.props;
|
||||
return (
|
||||
@@ -43,12 +48,26 @@ class AddGroup extends React.Component<Props, State> {
|
||||
<GroupForm
|
||||
submitForm={group => this.createGroup(group)}
|
||||
loading={loading}
|
||||
loadUserSuggestions={this.loadUserAutocompletion}
|
||||
/>
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
loadUserAutocompletion = (inputValue: string) => {
|
||||
const url = this.props.autocompleteLink + "?q=";
|
||||
return fetch(url + inputValue)
|
||||
.then(response => response.json())
|
||||
.then(json => {
|
||||
return json.map(element => {
|
||||
return {
|
||||
value: element,
|
||||
label: `${element.displayName} (${element.id})`
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
groupCreated = () => {
|
||||
this.props.history.push("/groups");
|
||||
};
|
||||
@@ -71,10 +90,12 @@ const mapStateToProps = state => {
|
||||
const loading = isCreateGroupPending(state);
|
||||
const error = getCreateGroupFailure(state);
|
||||
const createLink = getGroupsLink(state);
|
||||
const autocompleteLink = getUserAutoCompleteLink(state);
|
||||
return {
|
||||
createLink,
|
||||
loading,
|
||||
error
|
||||
error,
|
||||
autocompleteLink
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -3,21 +3,23 @@ import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import GroupForm from "../components/GroupForm";
|
||||
import {
|
||||
modifyGroup,
|
||||
modifyGroupReset,
|
||||
getModifyGroupFailure,
|
||||
isModifyGroupPending,
|
||||
getModifyGroupFailure
|
||||
modifyGroup,
|
||||
modifyGroupReset
|
||||
} from "../modules/groups";
|
||||
import type { History } from "history";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import type { Group } from "@scm-manager/ui-types";
|
||||
import { ErrorNotification } from "@scm-manager/ui-components";
|
||||
import { getUserAutoCompleteLink } from "../../modules/indexResource";
|
||||
|
||||
type Props = {
|
||||
group: Group,
|
||||
modifyGroup: (group: Group, callback?: () => void) => void,
|
||||
modifyGroupReset: Group => void,
|
||||
fetchGroup: (name: string) => void,
|
||||
autocompleteLink: string,
|
||||
history: History,
|
||||
loading?: boolean,
|
||||
error: Error
|
||||
@@ -37,6 +39,20 @@ class EditGroup extends React.Component<Props> {
|
||||
this.props.modifyGroup(group, this.groupModified(group));
|
||||
};
|
||||
|
||||
loadUserAutocompletion = (inputValue: string) => {
|
||||
const url = this.props.autocompleteLink + "?q=";
|
||||
return fetch(url + inputValue)
|
||||
.then(response => response.json())
|
||||
.then(json => {
|
||||
return json.map(element => {
|
||||
return {
|
||||
value: element,
|
||||
label: `${element.displayName} (${element.id})`
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { group, loading, error } = this.props;
|
||||
return (
|
||||
@@ -48,6 +64,7 @@ class EditGroup extends React.Component<Props> {
|
||||
this.modifyGroup(group);
|
||||
}}
|
||||
loading={loading}
|
||||
loadUserSuggestions={this.loadUserAutocompletion}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -57,9 +74,11 @@ class EditGroup extends React.Component<Props> {
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const loading = isModifyGroupPending(state, ownProps.group.name);
|
||||
const error = getModifyGroupFailure(state, ownProps.group.name);
|
||||
const autocompleteLink = getUserAutoCompleteLink(state);
|
||||
return {
|
||||
loading,
|
||||
error
|
||||
error,
|
||||
autocompleteLink
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -54,9 +54,8 @@ export function fetchGroupsByLink(link: string) {
|
||||
.then(data => {
|
||||
dispatch(fetchGroupsSuccess(data));
|
||||
})
|
||||
.catch(cause => {
|
||||
const error = new Error(`could not fetch groups: ${cause.message}`);
|
||||
dispatch(fetchGroupsFailure(link, error));
|
||||
.catch(err => {
|
||||
dispatch(fetchGroupsFailure(link, err));
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -105,9 +104,8 @@ function fetchGroup(link: string, name: string) {
|
||||
.then(data => {
|
||||
dispatch(fetchGroupSuccess(data));
|
||||
})
|
||||
.catch(cause => {
|
||||
const error = new Error(`could not fetch group: ${cause.message}`);
|
||||
dispatch(fetchGroupFailure(name, error));
|
||||
.catch(err => {
|
||||
dispatch(fetchGroupFailure(name, err));
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -151,10 +149,10 @@ export function createGroup(link: string, group: Group, callback?: () => void) {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
.catch(err => {
|
||||
dispatch(
|
||||
createGroupFailure(
|
||||
new Error(`Failed to create group ${group.name}: ${error.message}`)
|
||||
err
|
||||
)
|
||||
);
|
||||
});
|
||||
@@ -201,11 +199,11 @@ export function modifyGroup(group: Group, callback?: () => void) {
|
||||
.then(() => {
|
||||
dispatch(fetchGroupByLink(group));
|
||||
})
|
||||
.catch(cause => {
|
||||
.catch(err => {
|
||||
dispatch(
|
||||
modifyGroupFailure(
|
||||
group,
|
||||
new Error(`could not modify group ${group.name}: ${cause.message}`)
|
||||
err
|
||||
)
|
||||
);
|
||||
});
|
||||
@@ -259,11 +257,8 @@ export function deleteGroup(group: Group, callback?: () => void) {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
.catch(cause => {
|
||||
const error = new Error(
|
||||
`could not delete group ${group.name}: ${cause.message}`
|
||||
);
|
||||
dispatch(deleteGroupFailure(group, error));
|
||||
.catch(err => {
|
||||
dispatch(deleteGroupFailure(group, err));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
import type { Me } from "@scm-manager/ui-types";
|
||||
import * as types from "./types";
|
||||
|
||||
import {
|
||||
apiClient,
|
||||
UNAUTHORIZED_ERROR_MESSAGE
|
||||
} from "@scm-manager/ui-components";
|
||||
import { apiClient, UNAUTHORIZED_ERROR } from "@scm-manager/ui-components";
|
||||
import { isPending } from "./pending";
|
||||
import { getFailure } from "./failure";
|
||||
import {
|
||||
@@ -190,7 +187,7 @@ export const fetchMe = (link: string) => {
|
||||
dispatch(fetchMeSuccess(me));
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
if (error.message === UNAUTHORIZED_ERROR_MESSAGE) {
|
||||
if (error === UNAUTHORIZED_ERROR) {
|
||||
dispatch(fetchMeUnauthenticated());
|
||||
} else {
|
||||
dispatch(fetchMeFailure(error));
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import * as types from "./types";
|
||||
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
import type { Action, IndexResources } from "@scm-manager/ui-types";
|
||||
import type { Action, IndexResources, Link } from "@scm-manager/ui-types";
|
||||
import { isPending } from "./pending";
|
||||
import { getFailure } from "./failure";
|
||||
|
||||
@@ -100,6 +100,13 @@ export function getLink(state: Object, name: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getLinkCollection(state: Object, name: string): Link[] {
|
||||
if (state.indexResources.links && state.indexResources.links[name]) {
|
||||
return state.indexResources.links[name];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function getUiPluginsLink(state: Object) {
|
||||
return getLink(state, "uiPlugins");
|
||||
}
|
||||
@@ -143,3 +150,23 @@ export function getGitConfigLink(state: Object) {
|
||||
export function getSvnConfigLink(state: Object) {
|
||||
return getLink(state, "svnConfig");
|
||||
}
|
||||
|
||||
export function getUserAutoCompleteLink(state: Object): string {
|
||||
const link = getLinkCollection(state, "autocomplete").find(
|
||||
i => i.name === "users"
|
||||
);
|
||||
if (link) {
|
||||
return link.href;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function getGroupAutoCompleteLink(state: Object): string {
|
||||
const link = getLinkCollection(state, "autocomplete").find(
|
||||
i => i.name === "groups"
|
||||
);
|
||||
if (link) {
|
||||
return link.href;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -20,7 +20,11 @@ import reducer, {
|
||||
getHgConfigLink,
|
||||
getGitConfigLink,
|
||||
getSvnConfigLink,
|
||||
getLinks, getGroupsLink
|
||||
getLinks,
|
||||
getGroupsLink,
|
||||
getLinkCollection,
|
||||
getUserAutoCompleteLink,
|
||||
getGroupAutoCompleteLink
|
||||
} from "./indexResource";
|
||||
|
||||
const indexResourcesUnauthenticated = {
|
||||
@@ -73,354 +77,404 @@ const indexResourcesAuthenticated = {
|
||||
},
|
||||
svnConfig: {
|
||||
href: "http://localhost:8081/scm/api/v2/config/svn"
|
||||
}
|
||||
},
|
||||
autocomplete: [
|
||||
{
|
||||
href: "http://localhost:8081/scm/api/v2/autocomplete/users",
|
||||
name: "users"
|
||||
},
|
||||
{
|
||||
href: "http://localhost:8081/scm/api/v2/autocomplete/groups",
|
||||
name: "groups"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
describe("fetch index resource", () => {
|
||||
const index_url = "/api/v2/";
|
||||
const mockStore = configureMockStore([thunk]);
|
||||
describe("index resource", () => {
|
||||
describe("fetch index resource", () => {
|
||||
const index_url = "/api/v2/";
|
||||
const mockStore = configureMockStore([thunk]);
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
fetchMock.restore();
|
||||
});
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it("should successfully fetch index resources when unauthenticated", () => {
|
||||
fetchMock.getOnce(index_url, indexResourcesUnauthenticated);
|
||||
it("should successfully fetch index resources when unauthenticated", () => {
|
||||
fetchMock.getOnce(index_url, indexResourcesUnauthenticated);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: FETCH_INDEXRESOURCES_PENDING },
|
||||
{
|
||||
type: FETCH_INDEXRESOURCES_SUCCESS,
|
||||
payload: indexResourcesUnauthenticated
|
||||
}
|
||||
];
|
||||
const expectedActions = [
|
||||
{ type: FETCH_INDEXRESOURCES_PENDING },
|
||||
{
|
||||
type: FETCH_INDEXRESOURCES_SUCCESS,
|
||||
payload: indexResourcesUnauthenticated
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchIndexResources()).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchIndexResources()).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should successfully fetch index resources when authenticated", () => {
|
||||
fetchMock.getOnce(index_url, indexResourcesAuthenticated);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: FETCH_INDEXRESOURCES_PENDING },
|
||||
{
|
||||
type: FETCH_INDEXRESOURCES_SUCCESS,
|
||||
payload: indexResourcesAuthenticated
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchIndexResources()).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch FETCH_INDEX_RESOURCES_FAILURE if request fails", () => {
|
||||
fetchMock.getOnce(index_url, {
|
||||
status: 500
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchIndexResources()).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(FETCH_INDEXRESOURCES_PENDING);
|
||||
expect(actions[1].type).toEqual(FETCH_INDEXRESOURCES_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should successfully fetch index resources when authenticated", () => {
|
||||
fetchMock.getOnce(index_url, indexResourcesAuthenticated);
|
||||
describe("index resources reducer", () => {
|
||||
it("should return empty object, if state and action is undefined", () => {
|
||||
expect(reducer()).toEqual({});
|
||||
});
|
||||
|
||||
const expectedActions = [
|
||||
{ type: FETCH_INDEXRESOURCES_PENDING },
|
||||
{
|
||||
type: FETCH_INDEXRESOURCES_SUCCESS,
|
||||
payload: indexResourcesAuthenticated
|
||||
}
|
||||
];
|
||||
it("should return the same state, if the action is undefined", () => {
|
||||
const state = { x: true };
|
||||
expect(reducer(state)).toBe(state);
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchIndexResources()).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
it("should return the same state, if the action is unknown to the reducer", () => {
|
||||
const state = { x: true };
|
||||
expect(reducer(state, { type: "EL_SPECIALE" })).toBe(state);
|
||||
});
|
||||
|
||||
it("should store the index resources on FETCH_INDEXRESOURCES_SUCCESS", () => {
|
||||
const newState = reducer(
|
||||
{},
|
||||
fetchIndexResourcesSuccess(indexResourcesAuthenticated)
|
||||
);
|
||||
expect(newState.links).toBe(indexResourcesAuthenticated._links);
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch FETCH_INDEX_RESOURCES_FAILURE if request fails", () => {
|
||||
fetchMock.getOnce(index_url, {
|
||||
status: 500
|
||||
describe("index resources selectors", () => {
|
||||
const error = new Error("something goes wrong");
|
||||
|
||||
it("should return true, when fetch index resources is pending", () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[FETCH_INDEXRESOURCES]: true
|
||||
}
|
||||
};
|
||||
expect(isFetchIndexResourcesPending(state)).toEqual(true);
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchIndexResources()).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(FETCH_INDEXRESOURCES_PENDING);
|
||||
expect(actions[1].type).toEqual(FETCH_INDEXRESOURCES_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
it("should return false, when fetch index resources is not pending", () => {
|
||||
expect(isFetchIndexResourcesPending({})).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return error when fetch index resources did fail", () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[FETCH_INDEXRESOURCES]: error
|
||||
}
|
||||
};
|
||||
expect(getFetchIndexResourcesFailure(state)).toEqual(error);
|
||||
});
|
||||
|
||||
it("should return undefined when fetch index resources did not fail", () => {
|
||||
expect(getFetchIndexResourcesFailure({})).toBe(undefined);
|
||||
});
|
||||
|
||||
it("should return all links", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getLinks(state)).toBe(indexResourcesAuthenticated._links);
|
||||
});
|
||||
|
||||
// ui plugins link
|
||||
it("should return ui plugins link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getUiPluginsLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/ui/plugins"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return ui plugins links when unauthenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getUiPluginsLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/ui/plugins"
|
||||
);
|
||||
});
|
||||
|
||||
// me link
|
||||
it("should return me link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getMeLink(state)).toBe("http://localhost:8081/scm/api/v2/me/");
|
||||
});
|
||||
|
||||
it("should return undefined for me link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getMeLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// logout link
|
||||
it("should return logout link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getLogoutLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/auth/access_token"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return undefined for logout link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getLogoutLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// login link
|
||||
it("should return login link when unauthenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getLoginLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/auth/access_token"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return undefined for login link when authenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getLoginLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// users link
|
||||
it("should return users link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getUsersLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/users/"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return undefined for users link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getUsersLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// groups link
|
||||
it("should return groups link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getGroupsLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/groups/"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return undefined for groups link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getGroupsLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// config link
|
||||
it("should return config link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getConfigLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/config"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return undefined for config link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getConfigLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// repositories link
|
||||
it("should return repositories link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getRepositoriesLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/repositories/"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return config for repositories link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getRepositoriesLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// hgConfig link
|
||||
it("should return hgConfig link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getHgConfigLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/config/hg"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return config for hgConfig link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getHgConfigLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// gitConfig link
|
||||
it("should return gitConfig link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getGitConfigLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/config/git"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return config for gitConfig link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getGitConfigLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// svnConfig link
|
||||
it("should return svnConfig link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getSvnConfigLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/config/svn"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return config for svnConfig link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getSvnConfigLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// Autocomplete links
|
||||
it("should return link collection", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getLinkCollection(state, "autocomplete")).toEqual(
|
||||
indexResourcesAuthenticated._links.autocomplete
|
||||
);
|
||||
});
|
||||
|
||||
it("should return user autocomplete link", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getUserAutoCompleteLink(state)).toEqual(
|
||||
"http://localhost:8081/scm/api/v2/autocomplete/users"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return group autocomplete link", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getGroupAutoCompleteLink(state)).toEqual(
|
||||
"http://localhost:8081/scm/api/v2/autocomplete/groups"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("index resources reducer", () => {
|
||||
it("should return empty object, if state and action is undefined", () => {
|
||||
expect(reducer()).toEqual({});
|
||||
});
|
||||
|
||||
it("should return the same state, if the action is undefined", () => {
|
||||
const state = { x: true };
|
||||
expect(reducer(state)).toBe(state);
|
||||
});
|
||||
|
||||
it("should return the same state, if the action is unknown to the reducer", () => {
|
||||
const state = { x: true };
|
||||
expect(reducer(state, { type: "EL_SPECIALE" })).toBe(state);
|
||||
});
|
||||
|
||||
it("should store the index resources on FETCH_INDEXRESOURCES_SUCCESS", () => {
|
||||
const newState = reducer(
|
||||
{},
|
||||
fetchIndexResourcesSuccess(indexResourcesAuthenticated)
|
||||
);
|
||||
expect(newState.links).toBe(indexResourcesAuthenticated._links);
|
||||
});
|
||||
});
|
||||
|
||||
describe("index resources selectors", () => {
|
||||
const error = new Error("something goes wrong");
|
||||
|
||||
it("should return true, when fetch index resources is pending", () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[FETCH_INDEXRESOURCES]: true
|
||||
}
|
||||
};
|
||||
expect(isFetchIndexResourcesPending(state)).toEqual(true);
|
||||
});
|
||||
|
||||
it("should return false, when fetch index resources is not pending", () => {
|
||||
expect(isFetchIndexResourcesPending({})).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return error when fetch index resources did fail", () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[FETCH_INDEXRESOURCES]: error
|
||||
}
|
||||
};
|
||||
expect(getFetchIndexResourcesFailure(state)).toEqual(error);
|
||||
});
|
||||
|
||||
it("should return undefined when fetch index resources did not fail", () => {
|
||||
expect(getFetchIndexResourcesFailure({})).toBe(undefined);
|
||||
});
|
||||
|
||||
it("should return all links", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getLinks(state)).toBe(indexResourcesAuthenticated._links);
|
||||
});
|
||||
|
||||
// ui plugins link
|
||||
it("should return ui plugins link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getUiPluginsLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/ui/plugins"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return ui plugins links when unauthenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getUiPluginsLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/ui/plugins"
|
||||
);
|
||||
});
|
||||
|
||||
// me link
|
||||
it("should return me link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getMeLink(state)).toBe("http://localhost:8081/scm/api/v2/me/");
|
||||
});
|
||||
|
||||
it("should return undefined for me link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getMeLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// logout link
|
||||
it("should return logout link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getLogoutLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/auth/access_token"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return undefined for logout link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getLogoutLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// login link
|
||||
it("should return login link when unauthenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getLoginLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/auth/access_token"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return undefined for login link when authenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getLoginLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// users link
|
||||
it("should return users link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getUsersLink(state)).toBe("http://localhost:8081/scm/api/v2/users/");
|
||||
});
|
||||
|
||||
it("should return undefined for users link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getUsersLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// groups link
|
||||
it("should return groups link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getGroupsLink(state)).toBe("http://localhost:8081/scm/api/v2/groups/");
|
||||
});
|
||||
|
||||
it("should return undefined for groups link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getGroupsLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// config link
|
||||
it("should return config link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getConfigLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/config"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return undefined for config link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getConfigLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// repositories link
|
||||
it("should return repositories link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getRepositoriesLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/repositories/"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return config for repositories link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getRepositoriesLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// hgConfig link
|
||||
it("should return hgConfig link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getHgConfigLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/config/hg"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return config for hgConfig link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getHgConfigLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// gitConfig link
|
||||
it("should return gitConfig link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getGitConfigLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/config/git"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return config for gitConfig link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getGitConfigLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// svnConfig link
|
||||
it("should return svnConfig link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getSvnConfigLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/config/svn"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return config for svnConfig link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getSvnConfigLink(state)).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
//@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;
|
||||
@@ -1,18 +0,0 @@
|
||||
//@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;
|
||||
@@ -1,37 +0,0 @@
|
||||
//@flow
|
||||
|
||||
import React from "react";
|
||||
import type { Changeset } from "@scm-manager/ui-types";
|
||||
|
||||
type Props = {
|
||||
changeset: Changeset
|
||||
};
|
||||
|
||||
export default class ChangesetAuthor extends React.Component<Props> {
|
||||
render() {
|
||||
const { changeset } = this.props;
|
||||
if (!changeset.author) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { name } = changeset.author;
|
||||
return (
|
||||
<>
|
||||
{name} {this.renderMail()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
renderMail() {
|
||||
const { mail } = this.props.changeset.author;
|
||||
if (mail) {
|
||||
return (
|
||||
<a className="is-hidden-mobile" href={"mailto:" + mail}>
|
||||
<
|
||||
{mail}
|
||||
>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,20 @@ import React from "react";
|
||||
import type { Changeset, Repository } from "@scm-manager/ui-types";
|
||||
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-manager/ui-components";
|
||||
import AvatarWrapper from "./AvatarWrapper";
|
||||
import AvatarImage from "./AvatarImage";
|
||||
|
||||
import {
|
||||
DateFromNow,
|
||||
ChangesetId,
|
||||
ChangesetTag,
|
||||
ChangesetAuthor,
|
||||
ChangesetDiff,
|
||||
AvatarWrapper,
|
||||
AvatarImage,
|
||||
changesets,
|
||||
} from "@scm-manager/ui-components";
|
||||
|
||||
import classNames from "classnames";
|
||||
import ChangesetId from "./ChangesetId";
|
||||
import type { Tag } from "@scm-manager/ui-types";
|
||||
import ScmDiff from "../../containers/ScmDiff";
|
||||
|
||||
const styles = {
|
||||
spacing: {
|
||||
@@ -31,12 +35,12 @@ class ChangesetDetails extends React.Component<Props> {
|
||||
render() {
|
||||
const { changeset, repository, classes } = this.props;
|
||||
|
||||
const description = parseDescription(changeset.description);
|
||||
const description = changesets.parseDescription(changeset.description);
|
||||
|
||||
const id = (
|
||||
<ChangesetId repository={repository} changeset={changeset} link={false} />
|
||||
<ChangesetId repository={repository} changeset={changeset} link={false}/>
|
||||
);
|
||||
const date = <DateFromNow date={changeset.date} />;
|
||||
const date = <DateFromNow date={changeset.date}/>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -45,12 +49,12 @@ class ChangesetDetails extends React.Component<Props> {
|
||||
<article className="media">
|
||||
<AvatarWrapper>
|
||||
<p className={classNames("image", "is-64x64", classes.spacing)}>
|
||||
<AvatarImage changeset={changeset} />
|
||||
<AvatarImage person={changeset.author} />
|
||||
</p>
|
||||
</AvatarWrapper>
|
||||
<div className="media-content">
|
||||
<p>
|
||||
<ChangesetAuthor changeset={changeset} />
|
||||
<ChangesetAuthor changeset={changeset}/>
|
||||
</p>
|
||||
<p>
|
||||
<Interpolate
|
||||
@@ -67,14 +71,14 @@ class ChangesetDetails extends React.Component<Props> {
|
||||
return (
|
||||
<span key={key}>
|
||||
{item}
|
||||
<br />
|
||||
<br/>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<ScmDiff changeset={changeset} sideBySide={false} />
|
||||
<ChangesetDiff changeset={changeset} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -91,7 +95,7 @@ class ChangesetDetails extends React.Component<Props> {
|
||||
return (
|
||||
<div className="level-item">
|
||||
{tags.map((tag: Tag) => {
|
||||
return <ChangesetTag key={tag.name} tag={tag} />;
|
||||
return <ChangesetTag key={tag.name} tag={tag}/>;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
//@flow
|
||||
|
||||
import {Link} from "react-router-dom";
|
||||
import React from "react";
|
||||
import type {Changeset, Repository} from "@scm-manager/ui-types";
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
changeset: Changeset,
|
||||
link: boolean
|
||||
};
|
||||
|
||||
export default class ChangesetId extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
link: true
|
||||
};
|
||||
|
||||
shortId = (changeset: Changeset) => {
|
||||
return changeset.id.substr(0, 7);
|
||||
};
|
||||
|
||||
renderLink = () => {
|
||||
const { changeset, repository } = this.props;
|
||||
return (
|
||||
<Link
|
||||
to={`/repo/${repository.namespace}/${repository.name}/changeset/${
|
||||
changeset.id
|
||||
}`}
|
||||
>
|
||||
{this.shortId(changeset)}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
renderText = () => {
|
||||
const { changeset } = this.props;
|
||||
return this.shortId(changeset);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { link } = this.props;
|
||||
if (link) {
|
||||
return this.renderLink();
|
||||
}
|
||||
return this.renderText();
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
// @flow
|
||||
import ChangesetRow from "./ChangesetRow";
|
||||
import React from "react";
|
||||
import type { Changeset, Repository } from "@scm-manager/ui-types";
|
||||
import classNames from "classnames";
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
changesets: Changeset[]
|
||||
};
|
||||
|
||||
class ChangesetList extends React.Component<Props> {
|
||||
render() {
|
||||
const { repository, changesets } = this.props;
|
||||
const content = changesets.map(changeset => {
|
||||
return (
|
||||
<ChangesetRow
|
||||
key={changeset.id}
|
||||
repository={repository}
|
||||
changeset={changeset}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return <div className={classNames("box")}>{content}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default ChangesetList;
|
||||
@@ -1,101 +0,0 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type {Changeset, Repository, Tag} from "@scm-manager/ui-types";
|
||||
import classNames from "classnames";
|
||||
import {Interpolate, translate} from "react-i18next";
|
||||
import ChangesetId from "./ChangesetId";
|
||||
import injectSheet from "react-jss";
|
||||
import {DateFromNow} from "@scm-manager/ui-components";
|
||||
import ChangesetAuthor from "./ChangesetAuthor";
|
||||
import ChangesetTag from "./ChangesetTag";
|
||||
import {compose} from "redux";
|
||||
import {parseDescription} from "./changesets";
|
||||
import AvatarWrapper from "./AvatarWrapper";
|
||||
import AvatarImage from "./AvatarImage";
|
||||
|
||||
const styles = {
|
||||
pointer: {
|
||||
cursor: "pointer"
|
||||
},
|
||||
changesetGroup: {
|
||||
marginBottom: "1em"
|
||||
},
|
||||
withOverflow: {
|
||||
overflow: "auto"
|
||||
}
|
||||
};
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
changeset: Changeset,
|
||||
t: any,
|
||||
classes: any
|
||||
};
|
||||
|
||||
class ChangesetRow extends React.Component<Props> {
|
||||
createLink = (changeset: Changeset) => {
|
||||
const { repository } = this.props;
|
||||
return <ChangesetId changeset={changeset} repository={repository} />;
|
||||
};
|
||||
|
||||
getTags = () => {
|
||||
const { changeset } = this.props;
|
||||
return changeset._embedded.tags || [];
|
||||
};
|
||||
|
||||
render() {
|
||||
const { changeset, classes } = this.props;
|
||||
const changesetLink = this.createLink(changeset);
|
||||
const dateFromNow = <DateFromNow date={changeset.date} />;
|
||||
const authorLine = <ChangesetAuthor changeset={changeset} />;
|
||||
const description = parseDescription(changeset.description);
|
||||
|
||||
return (
|
||||
<article className={classNames("media", classes.inner)}>
|
||||
<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="content">
|
||||
<p className="is-ellipsis-overflow">
|
||||
<strong>{description.title}</strong>
|
||||
<br />
|
||||
<Interpolate
|
||||
i18nKey="changesets.changeset.summary"
|
||||
id={changesetLink}
|
||||
time={dateFromNow}
|
||||
/>
|
||||
</p>{" "}
|
||||
<div className="is-size-7">{authorLine}</div>
|
||||
</div>
|
||||
</div>
|
||||
{this.renderTags()}
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
renderTags = () => {
|
||||
const tags = this.getTags();
|
||||
if (tags.length > 0) {
|
||||
return (
|
||||
<div className="media-right">
|
||||
{tags.map((tag: Tag) => {
|
||||
return <ChangesetTag key={tag.name} tag={tag} />;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
export default compose(
|
||||
injectSheet(styles),
|
||||
translate("repos")
|
||||
)(ChangesetRow);
|
||||
@@ -1,32 +0,0 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Tag } from "@scm-manager/ui-types";
|
||||
import injectSheet from "react-jss";
|
||||
import classNames from "classnames";
|
||||
|
||||
const styles = {
|
||||
spacing: {
|
||||
marginRight: "4px"
|
||||
}
|
||||
};
|
||||
|
||||
type Props = {
|
||||
tag: Tag,
|
||||
|
||||
// context props
|
||||
classes: Object
|
||||
};
|
||||
|
||||
class ChangesetTag extends React.Component<Props> {
|
||||
render() {
|
||||
const { tag, classes } = this.props;
|
||||
return (
|
||||
<span className="tag is-info">
|
||||
<span className={classNames("fa", "fa-tag", classes.spacing)} />{" "}
|
||||
{tag.name}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectSheet(styles)(ChangesetTag);
|
||||
@@ -1,25 +0,0 @@
|
||||
// @flow
|
||||
export type Description = {
|
||||
title: string,
|
||||
message: string
|
||||
};
|
||||
|
||||
export function parseDescription(description?: string): Description {
|
||||
const desc = description ? description : "";
|
||||
const lineBreak = desc.indexOf("\n");
|
||||
|
||||
let title;
|
||||
let message = "";
|
||||
|
||||
if (lineBreak > 0) {
|
||||
title = desc.substring(0, lineBreak);
|
||||
message = desc.substring(lineBreak + 1);
|
||||
} else {
|
||||
title = desc;
|
||||
}
|
||||
|
||||
return {
|
||||
title,
|
||||
message
|
||||
};
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
// @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");
|
||||
});
|
||||
|
||||
it("should return an empty description for undefined", () => {
|
||||
const desc = parseDescription();
|
||||
expect(desc.title).toBe("");
|
||||
expect(desc.message).toBe("");
|
||||
});
|
||||
});
|
||||
@@ -12,6 +12,9 @@ const styles = {
|
||||
zeroflex: {
|
||||
flexGrow: 0
|
||||
},
|
||||
minWidthOfLabel: {
|
||||
minWidth: "4.5rem"
|
||||
},
|
||||
wrapper: {
|
||||
padding: "1rem 1.5rem 0.25rem 1.5rem",
|
||||
border: "1px solid #eee",
|
||||
|
||||
@@ -12,8 +12,7 @@ import {
|
||||
} from "../modules/changesets";
|
||||
|
||||
import {connect} from "react-redux";
|
||||
import ChangesetList from "../components/changesets/ChangesetList";
|
||||
import {ErrorNotification, getPageFromMatch, LinkPaginator, Loading} from "@scm-manager/ui-components";
|
||||
import {ErrorNotification, getPageFromMatch, LinkPaginator, ChangesetList, Loading} from "@scm-manager/ui-components";
|
||||
import {compose} from "redux";
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -114,7 +114,7 @@ class RepositoryRoot extends React.Component<Props> {
|
||||
return (
|
||||
<Page title={repository.namespace + "/" + repository.name}>
|
||||
<div className="columns">
|
||||
<div className="column is-three-quarters">
|
||||
<div className="column is-three-quarters is-clipped">
|
||||
<Switch>
|
||||
<Route
|
||||
path={url}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React from "react";
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
import type { Changeset } from "@scm-manager/ui-types";
|
||||
import { Diff2Html } from "diff2html";
|
||||
|
||||
type Props = {
|
||||
changeset: Changeset,
|
||||
sideBySide: boolean
|
||||
};
|
||||
|
||||
type State = {
|
||||
diff: string,
|
||||
error?: Error
|
||||
};
|
||||
|
||||
class ScmDiff extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { diff: "" };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { changeset } = this.props;
|
||||
const url = changeset._links.diff.href+"?format=GIT";
|
||||
apiClient
|
||||
.get(url)
|
||||
.then(response => response.text())
|
||||
.then(text => this.setState({ ...this.state, diff: text }))
|
||||
.catch(error => this.setState({ ...this.state, error }));
|
||||
}
|
||||
|
||||
render() {
|
||||
const options = {
|
||||
inputFormat: "diff",
|
||||
outputFormat: this.props.sideBySide ? "side-by-side" : "line-by-line",
|
||||
showFiles: false,
|
||||
matching: "lines"
|
||||
};
|
||||
|
||||
const outputHtml = Diff2Html.getPrettyHtml(this.state.diff, options);
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line react/no-danger
|
||||
<div dangerouslySetInnerHTML={{ __html: outputHtml }} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ScmDiff;
|
||||
@@ -224,9 +224,8 @@ export function modifyRepo(repository: Repository, callback?: () => void) {
|
||||
.then(() => {
|
||||
dispatch(fetchRepoByLink(repository));
|
||||
})
|
||||
.catch(cause => {
|
||||
const error = new Error(`failed to modify repo: ${cause.message}`);
|
||||
dispatch(modifyRepoFailure(repository, error));
|
||||
.catch(err => {
|
||||
dispatch(modifyRepoFailure(repository, err));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import {translate} from "react-i18next";
|
||||
import {Checkbox, InputField, SubmitButton} from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
import { Autocomplete, SubmitButton } from "@scm-manager/ui-components";
|
||||
import TypeSelector from "./TypeSelector";
|
||||
import type {PermissionCollection, PermissionCreateEntry} from "@scm-manager/ui-types";
|
||||
import type {
|
||||
PermissionCollection,
|
||||
PermissionCreateEntry,
|
||||
SelectValue
|
||||
} from "@scm-manager/ui-types";
|
||||
import * as validator from "./permissionValidation";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
createPermission: (permission: PermissionCreateEntry) => void,
|
||||
loading: boolean,
|
||||
currentPermissions: PermissionCollection
|
||||
currentPermissions: PermissionCollection,
|
||||
groupAutoCompleteLink: string,
|
||||
userAutoCompleteLink: string
|
||||
};
|
||||
|
||||
type State = {
|
||||
name: string,
|
||||
type: string,
|
||||
groupPermission: boolean,
|
||||
valid: boolean
|
||||
valid: boolean,
|
||||
value?: SelectValue
|
||||
};
|
||||
|
||||
class CreatePermissionForm extends React.Component<Props, State> {
|
||||
@@ -28,13 +35,95 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
name: "",
|
||||
type: "READ",
|
||||
groupPermission: false,
|
||||
valid: true
|
||||
valid: true,
|
||||
value: undefined
|
||||
};
|
||||
}
|
||||
|
||||
permissionScopeChanged = event => {
|
||||
const groupPermission = event.target.value === "GROUP_PERMISSION";
|
||||
this.setState({
|
||||
groupPermission: groupPermission,
|
||||
valid: validator.isPermissionValid(
|
||||
this.state.name,
|
||||
groupPermission,
|
||||
this.props.currentPermissions
|
||||
)
|
||||
});
|
||||
this.setState({ ...this.state, groupPermission });
|
||||
};
|
||||
|
||||
loadUserAutocompletion = (inputValue: string) => {
|
||||
return this.loadAutocompletion(this.props.userAutoCompleteLink, inputValue);
|
||||
};
|
||||
|
||||
loadGroupAutocompletion = (inputValue: string) => {
|
||||
return this.loadAutocompletion(
|
||||
this.props.groupAutoCompleteLink,
|
||||
inputValue
|
||||
);
|
||||
};
|
||||
|
||||
loadAutocompletion(url: string, inputValue: string) {
|
||||
const link = url + "?q=";
|
||||
return fetch(link + inputValue)
|
||||
.then(response => response.json())
|
||||
.then(json => {
|
||||
return json.map(element => {
|
||||
const label = element.displayName
|
||||
? `${element.displayName} (${element.id})`
|
||||
: element.id;
|
||||
return {
|
||||
value: element,
|
||||
label
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
renderAutocompletionField = () => {
|
||||
const { t } = this.props;
|
||||
if (this.state.groupPermission) {
|
||||
return (
|
||||
<Autocomplete
|
||||
loadSuggestions={this.loadGroupAutocompletion}
|
||||
valueSelected={this.groupOrUserSelected}
|
||||
value={this.state.value}
|
||||
label={t("permission.group")}
|
||||
noOptionsMessage={t("permission.autocomplete.no-group-options")}
|
||||
loadingMessage={t("permission.autocomplete.loading")}
|
||||
placeholder={t("permission.autocomplete.group-placeholder")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Autocomplete
|
||||
loadSuggestions={this.loadUserAutocompletion}
|
||||
valueSelected={this.groupOrUserSelected}
|
||||
value={this.state.value}
|
||||
label={t("permission.user")}
|
||||
noOptionsMessage={t("permission.autocomplete.no-user-options")}
|
||||
loadingMessage={t("permission.autocomplete.loading")}
|
||||
placeholder={t("permission.autocomplete.user-placeholder")}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
groupOrUserSelected = (value: SelectValue) => {
|
||||
this.setState({
|
||||
value,
|
||||
name: value.value.id,
|
||||
valid: validator.isPermissionValid(
|
||||
value.value.id,
|
||||
this.state.groupPermission,
|
||||
this.props.currentPermissions
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, loading } = this.props;
|
||||
const { name, type, groupPermission } = this.state;
|
||||
|
||||
const { type } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -43,23 +132,32 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
{t("permission.add-permission.add-permission-heading")}
|
||||
</h2>
|
||||
<form onSubmit={this.submit}>
|
||||
<div className="control">
|
||||
<label className="radio">
|
||||
<input
|
||||
type="radio"
|
||||
name="permission_scope"
|
||||
checked={!this.state.groupPermission}
|
||||
value="USER_PERMISSION"
|
||||
onChange={this.permissionScopeChanged}
|
||||
/>
|
||||
{t("permission.user-permission")}
|
||||
</label>
|
||||
<label className="radio">
|
||||
<input
|
||||
type="radio"
|
||||
name="permission_scope"
|
||||
value="GROUP_PERMISSION"
|
||||
checked={this.state.groupPermission}
|
||||
onChange={this.permissionScopeChanged}
|
||||
/>
|
||||
{t("permission.group-permission")}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<div class="column is-three-quarters">
|
||||
<InputField
|
||||
label={t("permission.name")}
|
||||
value={name ? name : ""}
|
||||
onChange={this.handleNameChange}
|
||||
validationError={!this.state.valid}
|
||||
errorMessage={t("permission.add-permission.name-input-invalid")}
|
||||
helpText={t("permission.help.nameHelpText")}
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
label={t("permission.group-permission")}
|
||||
checked={groupPermission ? groupPermission : false}
|
||||
onChange={this.handleGroupPermissionChange}
|
||||
helpText={t("permission.help.groupPermissionHelpText")}
|
||||
/>
|
||||
{this.renderAutocompletionField()}
|
||||
</div>
|
||||
<div class="column is-one-quarter">
|
||||
<TypeSelector
|
||||
@@ -108,27 +206,6 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
type: type
|
||||
});
|
||||
};
|
||||
|
||||
handleNameChange = (name: string) => {
|
||||
this.setState({
|
||||
name: name,
|
||||
valid: validator.isPermissionValid(
|
||||
name,
|
||||
this.state.groupPermission,
|
||||
this.props.currentPermissions
|
||||
)
|
||||
});
|
||||
};
|
||||
handleGroupPermissionChange = (groupPermission: boolean) => {
|
||||
this.setState({
|
||||
groupPermission: groupPermission,
|
||||
valid: validator.isPermissionValid(
|
||||
this.state.name,
|
||||
groupPermission,
|
||||
this.props.currentPermissions
|
||||
)
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("repos")(CreatePermissionForm);
|
||||
|
||||
@@ -27,6 +27,10 @@ import SinglePermission from "./SinglePermission";
|
||||
import CreatePermissionForm from "../components/CreatePermissionForm";
|
||||
import type { History } from "history";
|
||||
import { getPermissionsLink } from "../../modules/repos";
|
||||
import {
|
||||
getGroupAutoCompleteLink,
|
||||
getUserAutoCompleteLink
|
||||
} from "../../../modules/indexResource";
|
||||
|
||||
type Props = {
|
||||
namespace: string,
|
||||
@@ -37,6 +41,8 @@ type Props = {
|
||||
hasPermissionToCreate: boolean,
|
||||
loadingCreatePermission: boolean,
|
||||
permissionsLink: string,
|
||||
groupAutoCompleteLink: string,
|
||||
userAutoCompleteLink: string,
|
||||
|
||||
//dispatch functions
|
||||
fetchPermissions: (link: string, namespace: string, repoName: string) => void,
|
||||
@@ -92,7 +98,9 @@ class Permissions extends React.Component<Props> {
|
||||
namespace,
|
||||
repoName,
|
||||
loadingCreatePermission,
|
||||
hasPermissionToCreate
|
||||
hasPermissionToCreate,
|
||||
userAutoCompleteLink,
|
||||
groupAutoCompleteLink
|
||||
} = this.props;
|
||||
if (error) {
|
||||
return (
|
||||
@@ -113,6 +121,8 @@ class Permissions extends React.Component<Props> {
|
||||
createPermission={permission => this.createPermission(permission)}
|
||||
loading={loadingCreatePermission}
|
||||
currentPermissions={permissions}
|
||||
userAutoCompleteLink={userAutoCompleteLink}
|
||||
groupAutoCompleteLink={groupAutoCompleteLink}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
@@ -165,6 +175,8 @@ const mapStateToProps = (state, ownProps) => {
|
||||
);
|
||||
const hasPermissionToCreate = hasCreatePermission(state, namespace, repoName);
|
||||
const permissionsLink = getPermissionsLink(state, namespace, repoName);
|
||||
const groupAutoCompleteLink = getGroupAutoCompleteLink(state);
|
||||
const userAutoCompleteLink = getUserAutoCompleteLink(state);
|
||||
return {
|
||||
namespace,
|
||||
repoName,
|
||||
@@ -173,7 +185,9 @@ const mapStateToProps = (state, ownProps) => {
|
||||
permissions,
|
||||
hasPermissionToCreate,
|
||||
loadingCreatePermission,
|
||||
permissionsLink
|
||||
permissionsLink,
|
||||
groupAutoCompleteLink,
|
||||
userAutoCompleteLink
|
||||
};
|
||||
};
|
||||
|
||||
@@ -189,7 +203,9 @@ const mapDispatchToProps = dispatch => {
|
||||
repoName: string,
|
||||
callback?: () => void
|
||||
) => {
|
||||
dispatch(createPermission(link, permission, namespace, repoName, callback));
|
||||
dispatch(
|
||||
createPermission(link, permission, namespace, repoName, callback)
|
||||
);
|
||||
},
|
||||
createPermissionReset: (namespace: string, repoName: string) => {
|
||||
dispatch(createPermissionReset(namespace, repoName));
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
// @flow
|
||||
|
||||
import type {Action} from "@scm-manager/ui-components";
|
||||
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 type {Permission, PermissionCollection, PermissionCreateEntry} from "@scm-manager/ui-types";
|
||||
import {isPending} from "../../../modules/pending";
|
||||
import {getFailure} from "../../../modules/failure";
|
||||
import {Dispatch} from "redux";
|
||||
import type {
|
||||
Permission,
|
||||
PermissionCollection,
|
||||
PermissionCreateEntry
|
||||
} 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_PENDING = `${FETCH_PERMISSIONS}_${
|
||||
@@ -141,13 +145,8 @@ export function modifyPermission(
|
||||
callback();
|
||||
}
|
||||
})
|
||||
.catch(cause => {
|
||||
const error = new Error(
|
||||
`failed to modify permission: ${cause.message}`
|
||||
);
|
||||
dispatch(
|
||||
modifyPermissionFailure(permission, error, namespace, repoName)
|
||||
);
|
||||
.catch(err => {
|
||||
dispatch(modifyPermissionFailure(permission, err, namespace, repoName));
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -241,15 +240,7 @@ export function createPermission(
|
||||
}
|
||||
})
|
||||
.catch(err =>
|
||||
dispatch(
|
||||
createPermissionFailure(
|
||||
new Error(
|
||||
`failed to add permission ${permission.name}: ${err.message}`
|
||||
),
|
||||
namespace,
|
||||
repoName
|
||||
)
|
||||
)
|
||||
dispatch(createPermissionFailure(err, namespace, repoName))
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -318,13 +309,8 @@ export function deletePermission(
|
||||
callback();
|
||||
}
|
||||
})
|
||||
.catch(cause => {
|
||||
const error = new Error(
|
||||
`could not delete permission ${permission.name}: ${cause.message}`
|
||||
);
|
||||
dispatch(
|
||||
deletePermissionFailure(permission, namespace, repoName, error)
|
||||
);
|
||||
.catch(err => {
|
||||
dispatch(deletePermissionFailure(permission, namespace, repoName, err));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -119,7 +119,9 @@ class FileTree extends React.Component<Props> {
|
||||
<th className="is-hidden-mobile">
|
||||
{t("sources.file-tree.lastModified")}
|
||||
</th>
|
||||
<th>{t("sources.file-tree.description")}</th>
|
||||
<th className="is-hidden-mobile">
|
||||
{t("sources.file-tree.description")}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -6,10 +6,14 @@ import FileSize from "./FileSize";
|
||||
import FileIcon from "./FileIcon";
|
||||
import { Link } from "react-router-dom";
|
||||
import type { File } from "@scm-manager/ui-types";
|
||||
import classNames from "classnames";
|
||||
|
||||
const styles = {
|
||||
iconColumn: {
|
||||
width: "16px"
|
||||
},
|
||||
wordBreakMinWidth: {
|
||||
minWidth: "10em"
|
||||
}
|
||||
};
|
||||
|
||||
@@ -71,12 +75,14 @@ class FileTreeLeaf extends React.Component<Props> {
|
||||
return (
|
||||
<tr>
|
||||
<td className={classes.iconColumn}>{this.createFileIcon(file)}</td>
|
||||
<td>{this.createFileName(file)}</td>
|
||||
<td className={classNames(classes.wordBreakMinWidth, "is-word-break")}>{this.createFileName(file)}</td>
|
||||
<td className="is-hidden-mobile">{fileSize}</td>
|
||||
<td className="is-hidden-mobile">
|
||||
<DateFromNow date={file.lastModified} />
|
||||
</td>
|
||||
<td>{file.description}</td>
|
||||
<td className={classNames(classes.wordBreakMinWidth, "is-word-break", "is-hidden-mobile")}>
|
||||
{file.description}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ class Content extends React.Component<Props, State> {
|
||||
classes.marginInHeader
|
||||
)}
|
||||
/>
|
||||
<span>{file.name}</span>
|
||||
<span className="is-word-break">{file.name}</span>
|
||||
</div>
|
||||
<div className="media-right">{selector}</div>
|
||||
</article>
|
||||
@@ -125,11 +125,11 @@ class Content extends React.Component<Props, State> {
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{t("sources.content.path")}</td>
|
||||
<td>{file.path}</td>
|
||||
<td className="is-word-break">{file.path}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("sources.content.branch")}</td>
|
||||
<td>{revision}</td>
|
||||
<td className="is-word-break">{revision}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("sources.content.size")}</td>
|
||||
@@ -141,7 +141,7 @@ class Content extends React.Component<Props, State> {
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("sources.content.description")}</td>
|
||||
<td>{description}</td>
|
||||
<td className="is-word-break">{description}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -9,10 +9,10 @@ import type {
|
||||
import {
|
||||
ErrorNotification,
|
||||
Loading,
|
||||
StatePaginator
|
||||
StatePaginator,
|
||||
ChangesetList
|
||||
} from "@scm-manager/ui-components";
|
||||
import { getHistory } from "./history";
|
||||
import ChangesetList from "../../components/changesets/ChangesetList";
|
||||
|
||||
type Props = {
|
||||
file: File,
|
||||
|
||||
@@ -25,8 +25,7 @@ export function fetchSources(
|
||||
dispatch(fetchSourcesSuccess(repository, revision, path, sources));
|
||||
})
|
||||
.catch(err => {
|
||||
const error = new Error(`failed to fetch sources: ${err.message}`);
|
||||
dispatch(fetchSourcesFailure(repository, revision, path, error));
|
||||
dispatch(fetchSourcesFailure(repository, revision, path, err));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ type State = {
|
||||
password: string,
|
||||
loading: boolean,
|
||||
error?: Error,
|
||||
passwordChanged: boolean
|
||||
passwordChanged: boolean,
|
||||
passwordValid: boolean
|
||||
};
|
||||
|
||||
class SetUserPassword extends React.Component<Props, State> {
|
||||
@@ -32,7 +33,8 @@ class SetUserPassword extends React.Component<Props, State> {
|
||||
passwordConfirmationError: false,
|
||||
validatePasswordError: false,
|
||||
validatePassword: "",
|
||||
passwordChanged: false
|
||||
passwordChanged: false,
|
||||
passwordValid: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -104,7 +106,7 @@ class SetUserPassword extends React.Component<Props, State> {
|
||||
key={this.state.passwordChanged ? "changed" : "unchanged"}
|
||||
/>
|
||||
<SubmitButton
|
||||
disabled={!this.state.password}
|
||||
disabled={!this.state.passwordValid}
|
||||
loading={loading}
|
||||
label={t("user-form.submit")}
|
||||
/>
|
||||
@@ -112,8 +114,8 @@ class SetUserPassword extends React.Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
passwordChanged = (password: string) => {
|
||||
this.setState({ ...this.state, password });
|
||||
passwordChanged = (password: string, passwordValid: boolean) => {
|
||||
this.setState({ ...this.state, password, passwordValid: (!!password && passwordValid) });
|
||||
};
|
||||
|
||||
onClose = () => {
|
||||
|
||||
@@ -22,7 +22,8 @@ type State = {
|
||||
user: User,
|
||||
mailValidationError: boolean,
|
||||
nameValidationError: boolean,
|
||||
displayNameValidationError: boolean
|
||||
displayNameValidationError: boolean,
|
||||
passwordValid: boolean
|
||||
};
|
||||
|
||||
class UserForm extends React.Component<Props, State> {
|
||||
@@ -41,7 +42,8 @@ class UserForm extends React.Component<Props, State> {
|
||||
},
|
||||
mailValidationError: false,
|
||||
displayNameValidationError: false,
|
||||
nameValidationError: false
|
||||
nameValidationError: false,
|
||||
passwordValid: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -61,7 +63,6 @@ class UserForm extends React.Component<Props, State> {
|
||||
|
||||
isValid = () => {
|
||||
const user = this.state.user;
|
||||
const passwordValid = this.props.user ? !this.isFalsy(user.password) : true;
|
||||
return !(
|
||||
this.state.nameValidationError ||
|
||||
this.state.mailValidationError ||
|
||||
@@ -69,7 +70,7 @@ class UserForm extends React.Component<Props, State> {
|
||||
this.isFalsy(user.name) ||
|
||||
this.isFalsy(user.displayName) ||
|
||||
this.isFalsy(user.mail) ||
|
||||
passwordValid
|
||||
!this.state.passwordValid
|
||||
);
|
||||
};
|
||||
|
||||
@@ -180,9 +181,10 @@ class UserForm extends React.Component<Props, State> {
|
||||
});
|
||||
};
|
||||
|
||||
handlePasswordChange = (password: string) => {
|
||||
handlePasswordChange = (password: string, passwordValid: boolean) => {
|
||||
this.setState({
|
||||
user: { ...this.state.user, password }
|
||||
user: { ...this.state.user, password },
|
||||
passwordValid: !this.isFalsy(password) && passwordValid
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -35,8 +35,6 @@ export const DELETE_USER_FAILURE = `${DELETE_USER}_${types.FAILURE_SUFFIX}`;
|
||||
|
||||
const CONTENT_TYPE_USER = "application/vnd.scmm-user+json;v=2";
|
||||
|
||||
// TODO i18n for error messages
|
||||
|
||||
// fetch users
|
||||
|
||||
export function fetchUsers(link: string) {
|
||||
@@ -57,9 +55,8 @@ export function fetchUsersByLink(link: string) {
|
||||
.then(data => {
|
||||
dispatch(fetchUsersSuccess(data));
|
||||
})
|
||||
.catch(cause => {
|
||||
const error = new Error(`could not fetch users: ${cause.message}`);
|
||||
dispatch(fetchUsersFailure(link, error));
|
||||
.catch(err => {
|
||||
dispatch(fetchUsersFailure(link, err));
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -108,9 +105,8 @@ function fetchUser(link: string, name: string) {
|
||||
.then(data => {
|
||||
dispatch(fetchUserSuccess(data));
|
||||
})
|
||||
.catch(cause => {
|
||||
const error = new Error(`could not fetch user: ${cause.message}`);
|
||||
dispatch(fetchUserFailure(name, error));
|
||||
.catch(err => {
|
||||
dispatch(fetchUserFailure(name, err));
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -155,13 +151,7 @@ export function createUser(link: string, user: User, callback?: () => void) {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
.catch(err =>
|
||||
dispatch(
|
||||
createUserFailure(
|
||||
new Error(`failed to add user ${user.name}: ${err.message}`)
|
||||
)
|
||||
)
|
||||
);
|
||||
.catch(err => dispatch(createUserFailure(err)));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -260,11 +250,8 @@ export function deleteUser(user: User, callback?: () => void) {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
.catch(cause => {
|
||||
const error = new Error(
|
||||
`could not delete user ${user.name}: ${cause.message}`
|
||||
);
|
||||
dispatch(deleteUserFailure(user, error));
|
||||
.catch(err => {
|
||||
dispatch(deleteUserFailure(user, err));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user