mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-11 16:05:44 +01:00
use reflow to migrate from flow to typescript
This commit is contained in:
@@ -1,15 +1,14 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Repository, Branch } from "@scm-manager/ui-types";
|
||||
import { ButtonAddons, Button } from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
import React from 'react';
|
||||
import { Repository, Branch } from '@scm-manager/ui-types';
|
||||
import { ButtonAddons, Button } from '@scm-manager/ui-components';
|
||||
import { translate } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
branch: Branch,
|
||||
repository: Repository;
|
||||
branch: Branch;
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
t: (p: string) => string;
|
||||
};
|
||||
|
||||
class BranchButtonGroup extends React.Component<Props> {
|
||||
@@ -28,13 +27,13 @@ class BranchButtonGroup extends React.Component<Props> {
|
||||
<Button
|
||||
link={changesetLink}
|
||||
icon="exchange-alt"
|
||||
label={t("branch.commits")}
|
||||
label={t('branch.commits')}
|
||||
reducedMobile={true}
|
||||
/>
|
||||
<Button
|
||||
link={sourcesLink}
|
||||
icon="code"
|
||||
label={t("branch.sources")}
|
||||
label={t('branch.sources')}
|
||||
reducedMobile={true}
|
||||
/>
|
||||
</ButtonAddons>
|
||||
@@ -42,4 +41,4 @@ class BranchButtonGroup extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(BranchButtonGroup);
|
||||
export default translate('repos')(BranchButtonGroup);
|
||||
@@ -1,15 +1,14 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Repository, Branch } from "@scm-manager/ui-types";
|
||||
import { translate } from "react-i18next";
|
||||
import BranchButtonGroup from "./BranchButtonGroup";
|
||||
import DefaultBranchTag from "./DefaultBranchTag";
|
||||
import React from 'react';
|
||||
import { Repository, Branch } from '@scm-manager/ui-types';
|
||||
import { translate } from 'react-i18next';
|
||||
import BranchButtonGroup from './BranchButtonGroup';
|
||||
import DefaultBranchTag from './DefaultBranchTag';
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
branch: Branch,
|
||||
repository: Repository;
|
||||
branch: Branch;
|
||||
// context props
|
||||
t: string => string
|
||||
t: (p: string) => string;
|
||||
};
|
||||
|
||||
class BranchDetail extends React.Component<Props> {
|
||||
@@ -19,7 +18,7 @@ class BranchDetail extends React.Component<Props> {
|
||||
return (
|
||||
<div className="media">
|
||||
<div className="media-content subtitle">
|
||||
<strong>{t("branch.name")}</strong> {branch.name}{" "}
|
||||
<strong>{t('branch.name')}</strong> {branch.name}{' '}
|
||||
<DefaultBranchTag defaultBranch={branch.defaultBranch} />
|
||||
</div>
|
||||
<div className="media-right">
|
||||
@@ -30,4 +29,4 @@ class BranchDetail extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(BranchDetail);
|
||||
export default translate('repos')(BranchDetail);
|
||||
@@ -1,29 +1,28 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { Repository, Branch, BranchRequest } from "@scm-manager/ui-types";
|
||||
import React from 'react';
|
||||
import { translate } from 'react-i18next';
|
||||
import { Repository, Branch, BranchRequest } from '@scm-manager/ui-types';
|
||||
import {
|
||||
Select,
|
||||
InputField,
|
||||
SubmitButton,
|
||||
validation as validator
|
||||
} from "@scm-manager/ui-components";
|
||||
import { orderBranches } from "../util/orderBranches";
|
||||
validation as validator,
|
||||
} from '@scm-manager/ui-components';
|
||||
import { orderBranches } from '../util/orderBranches';
|
||||
|
||||
type Props = {
|
||||
submitForm: BranchRequest => void,
|
||||
repository: Repository,
|
||||
branches: Branch[],
|
||||
loading?: boolean,
|
||||
transmittedName?: string,
|
||||
disabled?: boolean,
|
||||
t: string => string
|
||||
submitForm: (p: BranchRequest) => void;
|
||||
repository: Repository;
|
||||
branches: Branch[];
|
||||
loading?: boolean;
|
||||
transmittedName?: string;
|
||||
disabled?: boolean;
|
||||
t: (p: string) => string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
source?: string,
|
||||
name?: string,
|
||||
nameValidationError: boolean
|
||||
source?: string;
|
||||
name?: string;
|
||||
nameValidationError: boolean;
|
||||
};
|
||||
|
||||
class BranchForm extends React.Component<Props, State> {
|
||||
@@ -32,7 +31,7 @@ class BranchForm extends React.Component<Props, State> {
|
||||
|
||||
this.state = {
|
||||
nameValidationError: false,
|
||||
name: props.transmittedName
|
||||
name: props.transmittedName,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -54,7 +53,7 @@ class BranchForm extends React.Component<Props, State> {
|
||||
if (this.isValid()) {
|
||||
this.props.submitForm({
|
||||
name: this.state.name,
|
||||
parent: this.state.source
|
||||
parent: this.state.source,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -65,7 +64,7 @@ class BranchForm extends React.Component<Props, State> {
|
||||
orderBranches(branches);
|
||||
const options = branches.map(branch => ({
|
||||
label: branch.name,
|
||||
value: branch.name
|
||||
value: branch.name,
|
||||
}));
|
||||
|
||||
return (
|
||||
@@ -75,7 +74,7 @@ class BranchForm extends React.Component<Props, State> {
|
||||
<div className="column">
|
||||
<Select
|
||||
name="source"
|
||||
label={t("branches.create.source")}
|
||||
label={t('branches.create.source')}
|
||||
options={options}
|
||||
onChange={this.handleSourceChange}
|
||||
loading={loading}
|
||||
@@ -83,11 +82,11 @@ class BranchForm extends React.Component<Props, State> {
|
||||
/>
|
||||
<InputField
|
||||
name="name"
|
||||
label={t("branches.create.name")}
|
||||
label={t('branches.create.name')}
|
||||
onChange={this.handleNameChange}
|
||||
value={name ? name : ""}
|
||||
value={name ? name : ''}
|
||||
validationError={this.state.nameValidationError}
|
||||
errorMessage={t("validation.branch.nameInvalid")}
|
||||
errorMessage={t('validation.branch.nameInvalid')}
|
||||
disabled={!!transmittedName || disabled}
|
||||
/>
|
||||
</div>
|
||||
@@ -97,7 +96,7 @@ class BranchForm extends React.Component<Props, State> {
|
||||
<SubmitButton
|
||||
disabled={disabled || !this.isValid()}
|
||||
loading={loading}
|
||||
label={t("branches.create.submit")}
|
||||
label={t('branches.create.submit')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -109,7 +108,7 @@ class BranchForm extends React.Component<Props, State> {
|
||||
handleSourceChange = (source: string) => {
|
||||
this.setState({
|
||||
...this.state,
|
||||
source
|
||||
source,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -117,9 +116,9 @@ class BranchForm extends React.Component<Props, State> {
|
||||
this.setState({
|
||||
nameValidationError: !validator.isNameValid(name),
|
||||
...this.state,
|
||||
name
|
||||
name,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("repos")(BranchForm);
|
||||
export default translate('repos')(BranchForm);
|
||||
@@ -1,12 +1,11 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import type { Branch } from "@scm-manager/ui-types";
|
||||
import DefaultBranchTag from "./DefaultBranchTag";
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Branch } from '@scm-manager/ui-types';
|
||||
import DefaultBranchTag from './DefaultBranchTag';
|
||||
|
||||
type Props = {
|
||||
baseUrl: string,
|
||||
branch: Branch
|
||||
baseUrl: string;
|
||||
branch: Branch;
|
||||
};
|
||||
|
||||
class BranchRow extends React.Component<Props> {
|
||||
@@ -1,13 +1,12 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import BranchRow from "./BranchRow";
|
||||
import type { Branch } from "@scm-manager/ui-types";
|
||||
import React from 'react';
|
||||
import { translate } from 'react-i18next';
|
||||
import BranchRow from './BranchRow';
|
||||
import { Branch } from '@scm-manager/ui-types';
|
||||
|
||||
type Props = {
|
||||
baseUrl: string,
|
||||
t: string => string,
|
||||
branches: Branch[]
|
||||
baseUrl: string;
|
||||
t: (p: string) => string;
|
||||
branches: Branch[];
|
||||
};
|
||||
|
||||
class BranchTable extends React.Component<Props> {
|
||||
@@ -17,7 +16,7 @@ class BranchTable extends React.Component<Props> {
|
||||
<table className="card-table table is-hoverable is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t("branches.table.branches")}</th>
|
||||
<th>{t('branches.table.branches')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{this.renderRow()}</tbody>
|
||||
@@ -37,4 +36,4 @@ class BranchTable extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(BranchTable);
|
||||
export default translate('repos')(BranchTable);
|
||||
@@ -1,12 +1,11 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import BranchDetail from "./BranchDetail";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import type { Repository, Branch } from "@scm-manager/ui-types";
|
||||
import React from 'react';
|
||||
import BranchDetail from './BranchDetail';
|
||||
import { ExtensionPoint } from '@scm-manager/ui-extensions';
|
||||
import { Repository, Branch } from '@scm-manager/ui-types';
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
branch: Branch
|
||||
repository: Repository;
|
||||
branch: Branch;
|
||||
};
|
||||
|
||||
class BranchView extends React.Component<Props> {
|
||||
@@ -21,7 +20,10 @@ class BranchView extends React.Component<Props> {
|
||||
<ExtensionPoint
|
||||
name="repos.branch-details.information"
|
||||
renderAll={true}
|
||||
props={{ repository, branch }}
|
||||
props={{
|
||||
repository,
|
||||
branch,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,30 +0,0 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { Tag } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
defaultBranch?: boolean,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
};
|
||||
|
||||
const LeftMarginTag = styled(Tag)`
|
||||
vertical-align: inherit;
|
||||
margin-left: 0.75rem;
|
||||
`;
|
||||
|
||||
class DefaultBranchTag extends React.Component<Props> {
|
||||
render() {
|
||||
const { defaultBranch, t } = this.props;
|
||||
|
||||
if (defaultBranch) {
|
||||
return <LeftMarginTag color="dark" label={t("branch.defaultTag")} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(DefaultBranchTag);
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { translate } from 'react-i18next';
|
||||
import styled from 'styled-components';
|
||||
import { Tag } from '@scm-manager/ui-components';
|
||||
|
||||
type Props = {
|
||||
defaultBranch?: boolean;
|
||||
|
||||
// context props
|
||||
t: (p: string) => string;
|
||||
};
|
||||
|
||||
const LeftMarginTag = styled(Tag)`
|
||||
vertical-align: inherit;
|
||||
margin-left: 0.75rem;
|
||||
`;
|
||||
|
||||
class DefaultBranchTag extends React.Component<Props> {
|
||||
render() {
|
||||
const { defaultBranch, t } = this.props;
|
||||
|
||||
if (defaultBranch) {
|
||||
return <LeftMarginTag color="dark" label={t('branch.defaultTag')} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default translate('repos')(DefaultBranchTag);
|
||||
@@ -1,34 +1,33 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import BranchView from "../components/BranchView";
|
||||
import { connect } from "react-redux";
|
||||
import { Redirect, Route, Switch, withRouter } from "react-router-dom";
|
||||
import type { Repository, Branch } from "@scm-manager/ui-types";
|
||||
import React from 'react';
|
||||
import BranchView from '../components/BranchView';
|
||||
import { connect } from 'react-redux';
|
||||
import { Redirect, Route, Switch, withRouter } from 'react-router-dom';
|
||||
import { Repository, Branch } from '@scm-manager/ui-types';
|
||||
import {
|
||||
fetchBranch,
|
||||
getBranch,
|
||||
getFetchBranchFailure,
|
||||
isFetchBranchPending
|
||||
} from "../modules/branches";
|
||||
import { ErrorNotification, Loading } from "@scm-manager/ui-components";
|
||||
import type { History } from "history";
|
||||
import { NotFoundError } from "@scm-manager/ui-components";
|
||||
import queryString from "query-string";
|
||||
isFetchBranchPending,
|
||||
} from '../modules/branches';
|
||||
import { ErrorNotification, Loading } from '@scm-manager/ui-components';
|
||||
import { History } from 'history';
|
||||
import { NotFoundError } from '@scm-manager/ui-components';
|
||||
import queryString from 'query-string';
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
branchName: string,
|
||||
branch: Branch,
|
||||
loading: boolean,
|
||||
error?: Error,
|
||||
repository: Repository;
|
||||
branchName: string;
|
||||
branch: Branch;
|
||||
loading: boolean;
|
||||
error?: Error;
|
||||
|
||||
// context props
|
||||
history: History,
|
||||
match: any,
|
||||
location: any,
|
||||
history: History;
|
||||
match: any;
|
||||
location: any;
|
||||
|
||||
// dispatch functions
|
||||
fetchBranch: (repository: Repository, branchName: string) => void
|
||||
fetchBranch: (repository: Repository, branchName: string) => void;
|
||||
};
|
||||
|
||||
class BranchRoot extends React.Component<Props> {
|
||||
@@ -39,7 +38,7 @@ class BranchRoot extends React.Component<Props> {
|
||||
}
|
||||
|
||||
stripEndingSlash = (url: string) => {
|
||||
if (url.endsWith("/")) {
|
||||
if (url.endsWith('/')) {
|
||||
return url.substring(0, url.length - 1);
|
||||
}
|
||||
return url;
|
||||
@@ -57,13 +56,11 @@ class BranchRoot extends React.Component<Props> {
|
||||
if (error) {
|
||||
if (
|
||||
error instanceof NotFoundError &&
|
||||
queryString.parse(location.search).create === "true"
|
||||
queryString.parse(location.search).create === 'true'
|
||||
) {
|
||||
return (
|
||||
<Redirect
|
||||
to={`/repo/${repository.namespace}/${
|
||||
repository.name
|
||||
}/branches/create?name=${match.params.branch}`}
|
||||
to={`/repo/${repository.namespace}/${repository.name}/branches/create?name=${match.params.branch}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -100,7 +97,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
branchName,
|
||||
branch,
|
||||
loading,
|
||||
error
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -108,13 +105,13 @@ const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
fetchBranch: (repository: Repository, branchName: string) => {
|
||||
dispatch(fetchBranch(repository, branchName));
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default withRouter(
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(BranchRoot)
|
||||
mapDispatchToProps,
|
||||
)(BranchRoot),
|
||||
);
|
||||
@@ -1,42 +1,41 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
import {
|
||||
fetchBranches,
|
||||
getBranches,
|
||||
getFetchBranchesFailure,
|
||||
isFetchBranchesPending,
|
||||
isPermittedToCreateBranches
|
||||
} from "../modules/branches";
|
||||
import { orderBranches } from "../util/orderBranches";
|
||||
import { connect } from "react-redux";
|
||||
import type { Branch, Repository } from "@scm-manager/ui-types";
|
||||
import { compose } from "redux";
|
||||
import { translate } from "react-i18next";
|
||||
import { withRouter } from "react-router-dom";
|
||||
isPermittedToCreateBranches,
|
||||
} from '../modules/branches';
|
||||
import { orderBranches } from '../util/orderBranches';
|
||||
import { connect } from 'react-redux';
|
||||
import { Branch, Repository } from '@scm-manager/ui-types';
|
||||
import { compose } from 'redux';
|
||||
import { translate } from 'react-i18next';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import {
|
||||
CreateButton,
|
||||
ErrorNotification,
|
||||
Loading,
|
||||
Notification,
|
||||
Subtitle
|
||||
} from "@scm-manager/ui-components";
|
||||
import BranchTable from "../components/BranchTable";
|
||||
Subtitle,
|
||||
} from '@scm-manager/ui-components';
|
||||
import BranchTable from '../components/BranchTable';
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
baseUrl: string,
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
branches: Branch[],
|
||||
repository: Repository;
|
||||
baseUrl: string;
|
||||
loading: boolean;
|
||||
error: Error;
|
||||
branches: Branch[];
|
||||
|
||||
// dispatch props
|
||||
showCreateButton: boolean,
|
||||
fetchBranches: Repository => void,
|
||||
showCreateButton: boolean;
|
||||
fetchBranches: (p: Repository) => void;
|
||||
|
||||
// Context props
|
||||
history: any,
|
||||
match: any,
|
||||
t: string => string
|
||||
history: any;
|
||||
match: any;
|
||||
t: (p: string) => string;
|
||||
};
|
||||
|
||||
class BranchesOverview extends React.Component<Props> {
|
||||
@@ -58,7 +57,7 @@ class BranchesOverview extends React.Component<Props> {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Subtitle subtitle={t("branches.overview.title")} />
|
||||
<Subtitle subtitle={t('branches.overview.title')} />
|
||||
{this.renderBranchesTable()}
|
||||
{this.renderCreateButton()}
|
||||
</>
|
||||
@@ -71,7 +70,11 @@ class BranchesOverview extends React.Component<Props> {
|
||||
orderBranches(branches);
|
||||
return <BranchTable baseUrl={baseUrl} branches={branches} />;
|
||||
}
|
||||
return <Notification type="info">{t("branches.overview.noBranches")}</Notification>;
|
||||
return (
|
||||
<Notification type="info">
|
||||
{t('branches.overview.noBranches')}
|
||||
</Notification>
|
||||
);
|
||||
}
|
||||
|
||||
renderCreateButton() {
|
||||
@@ -79,7 +82,7 @@ class BranchesOverview extends React.Component<Props> {
|
||||
if (showCreateButton) {
|
||||
return (
|
||||
<CreateButton
|
||||
label={t("branches.overview.createButton")}
|
||||
label={t('branches.overview.createButton')}
|
||||
link="./create"
|
||||
/>
|
||||
);
|
||||
@@ -100,7 +103,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
loading,
|
||||
error,
|
||||
branches,
|
||||
showCreateButton
|
||||
showCreateButton,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -108,15 +111,15 @@ const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
fetchBranches: (repository: Repository) => {
|
||||
dispatch(fetchBranches(repository));
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default compose(
|
||||
translate("repos"),
|
||||
translate('repos'),
|
||||
withRouter,
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)
|
||||
mapDispatchToProps,
|
||||
),
|
||||
)(BranchesOverview);
|
||||
@@ -1,13 +1,12 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
import {
|
||||
ErrorNotification,
|
||||
Loading,
|
||||
Subtitle
|
||||
} from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
import BranchForm from "../components/BranchForm";
|
||||
import type { Repository, Branch, BranchRequest } from "@scm-manager/ui-types";
|
||||
Subtitle,
|
||||
} from '@scm-manager/ui-components';
|
||||
import { translate } from 'react-i18next';
|
||||
import BranchForm from '../components/BranchForm';
|
||||
import { Repository, Branch, BranchRequest } from '@scm-manager/ui-types';
|
||||
import {
|
||||
fetchBranches,
|
||||
getBranches,
|
||||
@@ -17,35 +16,35 @@ import {
|
||||
isCreateBranchPending,
|
||||
getCreateBranchFailure,
|
||||
isFetchBranchesPending,
|
||||
getFetchBranchesFailure
|
||||
} from "../modules/branches";
|
||||
import type { History } from "history";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import queryString from "query-string";
|
||||
getFetchBranchesFailure,
|
||||
} from '../modules/branches';
|
||||
import { History } from 'history';
|
||||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import queryString from 'query-string';
|
||||
|
||||
type Props = {
|
||||
loading?: boolean,
|
||||
error?: Error,
|
||||
repository: Repository,
|
||||
branches: Branch[],
|
||||
createBranchesLink: string,
|
||||
isPermittedToCreateBranches: boolean,
|
||||
loading?: boolean;
|
||||
error?: Error;
|
||||
repository: Repository;
|
||||
branches: Branch[];
|
||||
createBranchesLink: string;
|
||||
isPermittedToCreateBranches: boolean;
|
||||
|
||||
// dispatcher functions
|
||||
fetchBranches: Repository => void,
|
||||
fetchBranches: (p: Repository) => void;
|
||||
createBranch: (
|
||||
createLink: string,
|
||||
repository: Repository,
|
||||
branch: BranchRequest,
|
||||
callback?: (Branch) => void
|
||||
) => void,
|
||||
resetForm: Repository => void,
|
||||
callback?: (p: Branch) => void,
|
||||
) => void;
|
||||
resetForm: (p: Repository) => void;
|
||||
|
||||
// context objects
|
||||
t: string => string,
|
||||
history: History,
|
||||
location: any
|
||||
t: (p: string) => string;
|
||||
history: History;
|
||||
location: any;
|
||||
};
|
||||
|
||||
class CreateBranch extends React.Component<Props> {
|
||||
@@ -60,7 +59,7 @@ class CreateBranch extends React.Component<Props> {
|
||||
history.push(
|
||||
`/repo/${repository.namespace}/${
|
||||
repository.name
|
||||
}/branch/${encodeURIComponent(branch.name)}/info`
|
||||
}/branch/${encodeURIComponent(branch.name)}/info`,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -69,7 +68,7 @@ class CreateBranch extends React.Component<Props> {
|
||||
this.props.createBranchesLink,
|
||||
this.props.repository,
|
||||
branch,
|
||||
newBranch => this.branchCreated(newBranch)
|
||||
newBranch => this.branchCreated(newBranch),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -79,7 +78,15 @@ class CreateBranch extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, loading, error, repository, branches, createBranchesLink, location } = this.props;
|
||||
const {
|
||||
t,
|
||||
loading,
|
||||
error,
|
||||
repository,
|
||||
branches,
|
||||
createBranchesLink,
|
||||
location,
|
||||
} = this.props;
|
||||
|
||||
if (error) {
|
||||
return <ErrorNotification error={error} />;
|
||||
@@ -91,7 +98,7 @@ class CreateBranch extends React.Component<Props> {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Subtitle subtitle={t("branches.create.title")} />
|
||||
<Subtitle subtitle={t('branches.create.title')} />
|
||||
<BranchForm
|
||||
submitForm={branchRequest => this.createBranch(branchRequest)}
|
||||
loading={loading}
|
||||
@@ -114,13 +121,13 @@ const mapDispatchToProps = dispatch => {
|
||||
createLink: string,
|
||||
repository: Repository,
|
||||
branchRequest: BranchRequest,
|
||||
callback?: (newBranch: Branch) => void
|
||||
callback?: (newBranch: Branch) => void,
|
||||
) => {
|
||||
dispatch(createBranch(createLink, repository, branchRequest, callback));
|
||||
},
|
||||
resetForm: (repository: Repository) => {
|
||||
dispatch(createBranchReset(repository));
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -138,13 +145,13 @@ const mapStateToProps = (state, ownProps) => {
|
||||
loading,
|
||||
error,
|
||||
branches,
|
||||
createBranchesLink
|
||||
createBranchesLink,
|
||||
};
|
||||
};
|
||||
|
||||
export default withRouter(
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("repos")(CreateBranch))
|
||||
mapDispatchToProps,
|
||||
)(translate('repos')(CreateBranch)),
|
||||
);
|
||||
@@ -1,6 +1,6 @@
|
||||
import configureMockStore from "redux-mock-store";
|
||||
import thunk from "redux-thunk";
|
||||
import fetchMock from "fetch-mock";
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import reducer, {
|
||||
FETCH_BRANCHES,
|
||||
FETCH_BRANCHES_FAILURE,
|
||||
@@ -23,31 +23,46 @@ import reducer, {
|
||||
createBranch,
|
||||
isCreateBranchPending,
|
||||
getCreateBranchFailure,
|
||||
isPermittedToCreateBranches
|
||||
} from "./branches";
|
||||
isPermittedToCreateBranches,
|
||||
} from './branches';
|
||||
|
||||
const namespace = "foo";
|
||||
const name = "bar";
|
||||
const key = namespace + "/" + name;
|
||||
const namespace = 'foo';
|
||||
const name = 'bar';
|
||||
const key = namespace + '/' + name;
|
||||
const repository = {
|
||||
namespace: "foo",
|
||||
name: "bar",
|
||||
namespace: 'foo',
|
||||
name: 'bar',
|
||||
_links: {
|
||||
branches: {
|
||||
href: "http://scm/api/rest/v2/repositories/foo/bar/branches"
|
||||
}
|
||||
}
|
||||
href: 'http://scm/api/rest/v2/repositories/foo/bar/branches',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const branch1 = { name: "branch1", revision: "revision1" };
|
||||
const branch2 = { name: "branch2", revision: "revision2" };
|
||||
const branch3 = { name: "branch3", revision: "revision3" };
|
||||
const branchRequest = { name: "newBranch", source: "master" };
|
||||
const newBranch = { name: "newBranch", revision: "rev3" };
|
||||
const branch1 = {
|
||||
name: 'branch1',
|
||||
revision: 'revision1',
|
||||
};
|
||||
const branch2 = {
|
||||
name: 'branch2',
|
||||
revision: 'revision2',
|
||||
};
|
||||
const branch3 = {
|
||||
name: 'branch3',
|
||||
revision: 'revision3',
|
||||
};
|
||||
const branchRequest = {
|
||||
name: 'newBranch',
|
||||
source: 'master',
|
||||
};
|
||||
const newBranch = {
|
||||
name: 'newBranch',
|
||||
revision: 'rev3',
|
||||
};
|
||||
|
||||
describe("branches", () => {
|
||||
describe("fetch branches", () => {
|
||||
const URL = "http://scm/api/rest/v2/repositories/foo/bar/branches";
|
||||
describe('branches', () => {
|
||||
describe('fetch branches', () => {
|
||||
const URL = 'http://scm/api/rest/v2/repositories/foo/bar/branches';
|
||||
const mockStore = configureMockStore([thunk]);
|
||||
|
||||
afterEach(() => {
|
||||
@@ -55,22 +70,27 @@ describe("branches", () => {
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it("should fetch branches", () => {
|
||||
it('should fetch branches', () => {
|
||||
const collection = {};
|
||||
|
||||
fetchMock.getOnce(URL, "{}");
|
||||
fetchMock.getOnce(URL, '{}');
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_BRANCHES_PENDING,
|
||||
payload: { repository },
|
||||
itemId: key
|
||||
payload: {
|
||||
repository,
|
||||
},
|
||||
itemId: key,
|
||||
},
|
||||
{
|
||||
type: FETCH_BRANCHES_SUCCESS,
|
||||
payload: { data: collection, repository },
|
||||
itemId: key
|
||||
}
|
||||
payload: {
|
||||
data: collection,
|
||||
repository,
|
||||
},
|
||||
itemId: key,
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
@@ -79,7 +99,7 @@ describe("branches", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail fetching branches on HTTP 500", () => {
|
||||
it('should fail fetching branches on HTTP 500', () => {
|
||||
const collection = {};
|
||||
|
||||
fetchMock.getOnce(URL, 500);
|
||||
@@ -87,14 +107,19 @@ describe("branches", () => {
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_BRANCHES_PENDING,
|
||||
payload: { repository },
|
||||
itemId: key
|
||||
payload: {
|
||||
repository,
|
||||
},
|
||||
itemId: key,
|
||||
},
|
||||
{
|
||||
type: FETCH_BRANCHES_FAILURE,
|
||||
payload: { error: collection, repository },
|
||||
itemId: key
|
||||
}
|
||||
payload: {
|
||||
error: collection,
|
||||
repository,
|
||||
},
|
||||
itemId: key,
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
@@ -104,11 +129,11 @@ describe("branches", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should successfully fetch single branch", () => {
|
||||
fetchMock.getOnce(URL + "/branch1", branch1);
|
||||
it('should successfully fetch single branch', () => {
|
||||
fetchMock.getOnce(URL + '/branch1', branch1);
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchBranch(repository, "branch1")).then(() => {
|
||||
return store.dispatch(fetchBranch(repository, 'branch1')).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(FETCH_BRANCH_PENDING);
|
||||
expect(actions[1].type).toEqual(FETCH_BRANCH_SUCCESS);
|
||||
@@ -116,13 +141,13 @@ describe("branches", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail fetching single branch on HTTP 500", () => {
|
||||
fetchMock.getOnce(URL + "/branch2", {
|
||||
status: 500
|
||||
it('should fail fetching single branch on HTTP 500', () => {
|
||||
fetchMock.getOnce(URL + '/branch2', {
|
||||
status: 500,
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchBranch(repository, "branch2")).then(() => {
|
||||
return store.dispatch(fetchBranch(repository, 'branch2')).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(FETCH_BRANCH_PENDING);
|
||||
expect(actions[1].type).toEqual(FETCH_BRANCH_FAILURE);
|
||||
@@ -130,17 +155,17 @@ describe("branches", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should create a branch successfully", () => {
|
||||
it('should create a branch successfully', () => {
|
||||
//branchrequest answer
|
||||
fetchMock.postOnce(URL, {
|
||||
status: 201,
|
||||
headers: {
|
||||
location: URL + "/newBranch"
|
||||
}
|
||||
location: URL + '/newBranch',
|
||||
},
|
||||
});
|
||||
|
||||
//branch answer
|
||||
fetchMock.getOnce(URL + "/newBranch", newBranch);
|
||||
fetchMock.getOnce(URL + '/newBranch', newBranch);
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
@@ -152,17 +177,17 @@ describe("branches", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should call the callback with the branch from the location header", () => {
|
||||
it('should call the callback with the branch from the location header', () => {
|
||||
//branchrequest answer
|
||||
fetchMock.postOnce(URL, {
|
||||
status: 201,
|
||||
headers: {
|
||||
location: URL + "/newBranch"
|
||||
}
|
||||
location: URL + '/newBranch',
|
||||
},
|
||||
});
|
||||
|
||||
//branch answer
|
||||
fetchMock.getOnce(URL + "/newBranch", newBranch);
|
||||
fetchMock.getOnce(URL + '/newBranch', newBranch);
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
@@ -179,9 +204,9 @@ describe("branches", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail creating a branch on HTTP 500", () => {
|
||||
it('should fail creating a branch on HTTP 500', () => {
|
||||
fetchMock.postOnce(URL, {
|
||||
status: 500
|
||||
status: 500,
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
@@ -195,179 +220,179 @@ describe("branches", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("branches reducer", () => {
|
||||
describe('branches reducer', () => {
|
||||
const branches = {
|
||||
_embedded: {
|
||||
branches: [branch1, branch2]
|
||||
branches: [branch1, branch2],
|
||||
},
|
||||
_links: {
|
||||
self: {
|
||||
href: "/self"
|
||||
href: '/self',
|
||||
},
|
||||
create: {
|
||||
href: "/create"
|
||||
}
|
||||
}
|
||||
href: '/create',
|
||||
},
|
||||
},
|
||||
};
|
||||
const action = {
|
||||
type: FETCH_BRANCHES_SUCCESS,
|
||||
payload: {
|
||||
repository,
|
||||
data: branches
|
||||
}
|
||||
data: branches,
|
||||
},
|
||||
};
|
||||
|
||||
it("should store the branches", () => {
|
||||
it('should store the branches', () => {
|
||||
const newState = reducer({}, action);
|
||||
const repoState = newState["foo/bar"];
|
||||
const repoState = newState['foo/bar'];
|
||||
|
||||
expect(repoState.list._links.create.href).toEqual("/create");
|
||||
expect(repoState.list._embedded.branches).toEqual(["branch1", "branch2"]);
|
||||
expect(repoState.list._links.create.href).toEqual('/create');
|
||||
expect(repoState.list._embedded.branches).toEqual(['branch1', 'branch2']);
|
||||
|
||||
expect(repoState.byName.branch1).toEqual(branch1);
|
||||
expect(repoState.byName.branch2).toEqual(branch2);
|
||||
});
|
||||
|
||||
it("should store a single branch", () => {
|
||||
it('should store a single branch', () => {
|
||||
const newState = reducer({}, fetchBranchSuccess(repository, branch1));
|
||||
const repoState = newState["foo/bar"];
|
||||
const repoState = newState['foo/bar'];
|
||||
|
||||
expect(repoState.list).toBeUndefined();
|
||||
expect(repoState.byName.branch1).toEqual(branch1);
|
||||
});
|
||||
|
||||
it("should add a single branch", () => {
|
||||
it('should add a single branch', () => {
|
||||
const state = {
|
||||
"foo/bar": {
|
||||
'foo/bar': {
|
||||
list: {
|
||||
_links: {},
|
||||
_embedded: {
|
||||
branches: ["branch1"]
|
||||
}
|
||||
branches: ['branch1'],
|
||||
},
|
||||
},
|
||||
byName: {
|
||||
branch1: branch1
|
||||
}
|
||||
}
|
||||
branch1: branch1,
|
||||
},
|
||||
},
|
||||
};
|
||||
const newState = reducer(state, fetchBranchSuccess(repository, branch2));
|
||||
const repoState = newState["foo/bar"];
|
||||
const repoState = newState['foo/bar'];
|
||||
const byName = repoState.byName;
|
||||
|
||||
expect(repoState.list._embedded.branches).toEqual(["branch1"]);
|
||||
expect(repoState.list._embedded.branches).toEqual(['branch1']);
|
||||
expect(byName.branch1).toEqual(branch1);
|
||||
expect(byName.branch2).toEqual(branch2);
|
||||
});
|
||||
|
||||
it("should not overwrite non related repositories", () => {
|
||||
it('should not overwrite non related repositories', () => {
|
||||
const state = {
|
||||
"scm/core": {
|
||||
'scm/core': {
|
||||
byName: {
|
||||
branch1: branch1
|
||||
}
|
||||
}
|
||||
branch1: branch1,
|
||||
},
|
||||
},
|
||||
};
|
||||
const newState = reducer(state, fetchBranchSuccess(repository, branch1));
|
||||
const byName = newState["scm/core"].byName;
|
||||
const byName = newState['scm/core'].byName;
|
||||
|
||||
expect(byName.branch1).toEqual(branch1);
|
||||
});
|
||||
|
||||
it("should overwrite existing branch", () => {
|
||||
it('should overwrite existing branch', () => {
|
||||
const state = {
|
||||
"foo/bar": {
|
||||
'foo/bar': {
|
||||
byName: {
|
||||
branch1: {
|
||||
name: "branch1",
|
||||
revision: "xyz"
|
||||
}
|
||||
}
|
||||
}
|
||||
name: 'branch1',
|
||||
revision: 'xyz',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const newState = reducer(state, fetchBranchSuccess(repository, branch1));
|
||||
const byName = newState["foo/bar"].byName;
|
||||
const byName = newState['foo/bar'].byName;
|
||||
|
||||
expect(byName.branch1.revision).toEqual("revision1");
|
||||
expect(byName.branch1.revision).toEqual('revision1');
|
||||
});
|
||||
|
||||
it("should not overwrite existing branches", () => {
|
||||
it('should not overwrite existing branches', () => {
|
||||
const state = {
|
||||
"foo/bar": {
|
||||
'foo/bar': {
|
||||
byName: {
|
||||
branch1,
|
||||
branch2,
|
||||
branch3
|
||||
}
|
||||
}
|
||||
branch3,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const newState = reducer(state, action);
|
||||
expect(newState["foo/bar"].byName.branch1).toEqual(branch1);
|
||||
expect(newState["foo/bar"].byName.branch2).toEqual(branch2);
|
||||
expect(newState["foo/bar"].byName.branch3).toEqual(branch3);
|
||||
expect(newState['foo/bar'].byName.branch1).toEqual(branch1);
|
||||
expect(newState['foo/bar'].byName.branch2).toEqual(branch2);
|
||||
expect(newState['foo/bar'].byName.branch3).toEqual(branch3);
|
||||
});
|
||||
});
|
||||
|
||||
describe("branch selectors", () => {
|
||||
const error = new Error("Something went wrong");
|
||||
describe('branch selectors', () => {
|
||||
const error = new Error('Something went wrong');
|
||||
|
||||
const state = {
|
||||
branches: {
|
||||
"foo/bar": {
|
||||
'foo/bar': {
|
||||
list: {
|
||||
_links: {},
|
||||
_embedded: {
|
||||
branches: ["branch1", "branch2"]
|
||||
}
|
||||
branches: ['branch1', 'branch2'],
|
||||
},
|
||||
},
|
||||
byName: {
|
||||
branch1: branch1,
|
||||
branch2: branch2
|
||||
}
|
||||
}
|
||||
}
|
||||
branch2: branch2,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
it("should return true, when fetching branches is pending", () => {
|
||||
it('should return true, when fetching branches is pending', () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[FETCH_BRANCHES + "/foo/bar"]: true
|
||||
}
|
||||
[FETCH_BRANCHES + '/foo/bar']: true,
|
||||
},
|
||||
};
|
||||
|
||||
expect(isFetchBranchesPending(state, repository)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should return branches", () => {
|
||||
it('should return branches', () => {
|
||||
const branches = getBranches(state, repository);
|
||||
expect(branches.length).toEqual(2);
|
||||
expect(branches).toContain(branch1);
|
||||
expect(branches).toContain(branch2);
|
||||
});
|
||||
|
||||
it("should return always the same reference for branches", () => {
|
||||
it('should return always the same reference for branches', () => {
|
||||
const one = getBranches(state, repository);
|
||||
const two = getBranches(state, repository);
|
||||
expect(one).toBe(two);
|
||||
});
|
||||
|
||||
it("should not return cached reference, if branches have changed", () => {
|
||||
it('should not return cached reference, if branches have changed', () => {
|
||||
const one = getBranches(state, repository);
|
||||
const newState = {
|
||||
branches: {
|
||||
"foo/bar": {
|
||||
'foo/bar': {
|
||||
list: {
|
||||
_links: {},
|
||||
_embedded: {
|
||||
branches: ["branch2", "branch3"]
|
||||
}
|
||||
branches: ['branch2', 'branch3'],
|
||||
},
|
||||
},
|
||||
byName: {
|
||||
branch2,
|
||||
branch3
|
||||
}
|
||||
}
|
||||
}
|
||||
branch3,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const two = getBranches(newState, repository);
|
||||
expect(one).not.toBe(two);
|
||||
@@ -376,95 +401,100 @@ describe("branches", () => {
|
||||
expect(two).toContain(branch3);
|
||||
});
|
||||
|
||||
it("should return undefined, if no branches for the repository available", () => {
|
||||
const branches = getBranches({ branches: {} }, repository);
|
||||
it('should return undefined, if no branches for the repository available', () => {
|
||||
const branches = getBranches(
|
||||
{
|
||||
branches: {},
|
||||
},
|
||||
repository,
|
||||
);
|
||||
expect(branches).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return single branch by name", () => {
|
||||
const branch = getBranch(state, repository, "branch1");
|
||||
it('should return single branch by name', () => {
|
||||
const branch = getBranch(state, repository, 'branch1');
|
||||
expect(branch).toEqual(branch1);
|
||||
});
|
||||
|
||||
it("should return same reference for single branch by name", () => {
|
||||
const one = getBranch(state, repository, "branch1");
|
||||
const two = getBranch(state, repository, "branch1");
|
||||
it('should return same reference for single branch by name', () => {
|
||||
const one = getBranch(state, repository, 'branch1');
|
||||
const two = getBranch(state, repository, 'branch1');
|
||||
expect(one).toBe(two);
|
||||
});
|
||||
|
||||
it("should return undefined if branch does not exist", () => {
|
||||
const branch = getBranch(state, repository, "branch42");
|
||||
it('should return undefined if branch does not exist', () => {
|
||||
const branch = getBranch(state, repository, 'branch42');
|
||||
expect(branch).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return true if the branches list contains the create link", () => {
|
||||
it('should return true if the branches list contains the create link', () => {
|
||||
const stateWithLink = {
|
||||
branches: {
|
||||
"foo/bar": {
|
||||
...state.branches["foo/bar"],
|
||||
'foo/bar': {
|
||||
...state.branches['foo/bar'],
|
||||
list: {
|
||||
...state.branches["foo/bar"].list,
|
||||
...state.branches['foo/bar'].list,
|
||||
_links: {
|
||||
create: {
|
||||
href: "http://create-it"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
href: 'http://create-it',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const permitted = isPermittedToCreateBranches(stateWithLink, repository);
|
||||
expect(permitted).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false if the create link is missing", () => {
|
||||
it('should return false if the create link is missing', () => {
|
||||
const permitted = isPermittedToCreateBranches(state, repository);
|
||||
expect(permitted).toBe(false);
|
||||
});
|
||||
|
||||
it("should return error if fetching branches failed", () => {
|
||||
it('should return error if fetching branches failed', () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[FETCH_BRANCHES + "/foo/bar"]: error
|
||||
}
|
||||
[FETCH_BRANCHES + '/foo/bar']: error,
|
||||
},
|
||||
};
|
||||
|
||||
expect(getFetchBranchesFailure(state, repository)).toEqual(error);
|
||||
});
|
||||
|
||||
it("should return false if fetching branches did not fail", () => {
|
||||
it('should return false if fetching branches did not fail', () => {
|
||||
expect(getFetchBranchesFailure({}, repository)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return true if create branch is pending", () => {
|
||||
it('should return true if create branch is pending', () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[CREATE_BRANCH + "/foo/bar"]: true
|
||||
}
|
||||
[CREATE_BRANCH + '/foo/bar']: true,
|
||||
},
|
||||
};
|
||||
expect(isCreateBranchPending(state, repository)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false if create branch is not pending", () => {
|
||||
it('should return false if create branch is not pending', () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[CREATE_BRANCH + "/foo/bar"]: false
|
||||
}
|
||||
[CREATE_BRANCH + '/foo/bar']: false,
|
||||
},
|
||||
};
|
||||
expect(isCreateBranchPending(state, repository)).toBe(false);
|
||||
});
|
||||
|
||||
it("should return error when create branch did fail", () => {
|
||||
it('should return error when create branch did fail', () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[CREATE_BRANCH]: error
|
||||
}
|
||||
[CREATE_BRANCH]: error,
|
||||
},
|
||||
};
|
||||
expect(getCreateBranchFailure(state)).toEqual(error);
|
||||
});
|
||||
|
||||
it("should return undefined when create branch did not fail", () => {
|
||||
it('should return undefined when create branch did not fail', () => {
|
||||
expect(getCreateBranchFailure({})).toBe(undefined);
|
||||
});
|
||||
});
|
||||
@@ -1,40 +1,39 @@
|
||||
// @flow
|
||||
import {
|
||||
FAILURE_SUFFIX,
|
||||
PENDING_SUFFIX,
|
||||
SUCCESS_SUFFIX,
|
||||
RESET_SUFFIX
|
||||
} from "../../../modules/types";
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
import type {
|
||||
RESET_SUFFIX,
|
||||
} from '../../../modules/types';
|
||||
import { apiClient } from '@scm-manager/ui-components';
|
||||
import {
|
||||
Action,
|
||||
Branch,
|
||||
BranchRequest,
|
||||
Repository
|
||||
} from "@scm-manager/ui-types";
|
||||
import { isPending } from "../../../modules/pending";
|
||||
import { getFailure } from "../../../modules/failure";
|
||||
Repository,
|
||||
} from '@scm-manager/ui-types';
|
||||
import { isPending } from '../../../modules/pending';
|
||||
import { getFailure } from '../../../modules/failure';
|
||||
|
||||
import memoizeOne from "memoize-one";
|
||||
import memoizeOne from 'memoize-one';
|
||||
|
||||
export const FETCH_BRANCHES = "scm/repos/FETCH_BRANCHES";
|
||||
export const FETCH_BRANCHES = 'scm/repos/FETCH_BRANCHES';
|
||||
export const FETCH_BRANCHES_PENDING = `${FETCH_BRANCHES}_${PENDING_SUFFIX}`;
|
||||
export const FETCH_BRANCHES_SUCCESS = `${FETCH_BRANCHES}_${SUCCESS_SUFFIX}`;
|
||||
export const FETCH_BRANCHES_FAILURE = `${FETCH_BRANCHES}_${FAILURE_SUFFIX}`;
|
||||
|
||||
export const FETCH_BRANCH = "scm/repos/FETCH_BRANCH";
|
||||
export const FETCH_BRANCH = 'scm/repos/FETCH_BRANCH';
|
||||
export const FETCH_BRANCH_PENDING = `${FETCH_BRANCH}_${PENDING_SUFFIX}`;
|
||||
export const FETCH_BRANCH_SUCCESS = `${FETCH_BRANCH}_${SUCCESS_SUFFIX}`;
|
||||
export const FETCH_BRANCH_FAILURE = `${FETCH_BRANCH}_${FAILURE_SUFFIX}`;
|
||||
|
||||
export const CREATE_BRANCH = "scm/repos/CREATE_BRANCH";
|
||||
export const CREATE_BRANCH = 'scm/repos/CREATE_BRANCH';
|
||||
export const CREATE_BRANCH_PENDING = `${CREATE_BRANCH}_${PENDING_SUFFIX}`;
|
||||
export const CREATE_BRANCH_SUCCESS = `${CREATE_BRANCH}_${SUCCESS_SUFFIX}`;
|
||||
export const CREATE_BRANCH_FAILURE = `${CREATE_BRANCH}_${FAILURE_SUFFIX}`;
|
||||
export const CREATE_BRANCH_RESET = `${CREATE_BRANCH}_${RESET_SUFFIX}`;
|
||||
|
||||
const CONTENT_TYPE_BRANCH_REQUEST =
|
||||
"application/vnd.scmm-branchRequest+json;v=2";
|
||||
'application/vnd.scmm-branchRequest+json;v=2';
|
||||
|
||||
// Fetching branches
|
||||
|
||||
@@ -42,8 +41,11 @@ export function fetchBranches(repository: Repository) {
|
||||
if (!repository._links.branches) {
|
||||
return {
|
||||
type: FETCH_BRANCHES_SUCCESS,
|
||||
payload: { repository, data: {} },
|
||||
itemId: createKey(repository)
|
||||
payload: {
|
||||
repository,
|
||||
data: {},
|
||||
},
|
||||
itemId: createKey(repository),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -63,8 +65,8 @@ export function fetchBranches(repository: Repository) {
|
||||
|
||||
export function fetchBranch(repository: Repository, name: string) {
|
||||
let link = repository._links.branches.href;
|
||||
if (!link.endsWith("/")) {
|
||||
link += "/";
|
||||
if (!link.endsWith('/')) {
|
||||
link += '/';
|
||||
}
|
||||
link += encodeURIComponent(name);
|
||||
return function(dispatch: any) {
|
||||
@@ -89,13 +91,13 @@ export function createBranch(
|
||||
link: string,
|
||||
repository: Repository,
|
||||
branchRequest: BranchRequest,
|
||||
callback?: (branch: Branch) => void
|
||||
callback?: (branch: Branch) => void,
|
||||
) {
|
||||
return function(dispatch: any) {
|
||||
dispatch(createBranchPending(repository));
|
||||
return apiClient
|
||||
.post(link, branchRequest, CONTENT_TYPE_BRANCH_REQUEST)
|
||||
.then(response => response.headers.get("Location"))
|
||||
.then(response => response.headers.get('Location'))
|
||||
.then(location => apiClient.get(location))
|
||||
.then(response => response.json())
|
||||
.then(branch => {
|
||||
@@ -116,14 +118,14 @@ function collectBranches(repoState) {
|
||||
|
||||
const memoizedBranchCollector = memoizeOne(collectBranches);
|
||||
|
||||
export function getBranches(state: Object, repository: Repository) {
|
||||
export function getBranches(state: object, repository: Repository) {
|
||||
const repoState = getRepoState(state, repository);
|
||||
if (repoState && repoState.list) {
|
||||
return memoizedBranchCollector(repoState);
|
||||
}
|
||||
}
|
||||
|
||||
export function getBranchCreateLink(state: Object, repository: Repository) {
|
||||
export function getBranchCreateLink(state: object, repository: Repository) {
|
||||
const repoState = getRepoState(state, repository);
|
||||
if (
|
||||
repoState &&
|
||||
@@ -135,7 +137,7 @@ export function getBranchCreateLink(state: Object, repository: Repository) {
|
||||
}
|
||||
}
|
||||
|
||||
function getRepoState(state: Object, repository: Repository) {
|
||||
function getRepoState(state: object, repository: Repository) {
|
||||
const key = createKey(repository);
|
||||
const repoState = state.branches[key];
|
||||
if (repoState && repoState.byName) {
|
||||
@@ -144,8 +146,8 @@ function getRepoState(state: Object, repository: Repository) {
|
||||
}
|
||||
|
||||
export const isPermittedToCreateBranches = (
|
||||
state: Object,
|
||||
repository: Repository
|
||||
state: object,
|
||||
repository: Repository,
|
||||
): boolean => {
|
||||
const repoState = getRepoState(state, repository);
|
||||
return !!(
|
||||
@@ -157,10 +159,10 @@ export const isPermittedToCreateBranches = (
|
||||
};
|
||||
|
||||
export function getBranch(
|
||||
state: Object,
|
||||
state: object,
|
||||
repository: Repository,
|
||||
name: string
|
||||
): ?Branch {
|
||||
name: string,
|
||||
): Branch | null | undefined {
|
||||
const repoState = getRepoState(state, repository);
|
||||
if (repoState) {
|
||||
return repoState.byName[name];
|
||||
@@ -169,130 +171,157 @@ export function getBranch(
|
||||
|
||||
// Action creators
|
||||
export function isFetchBranchesPending(
|
||||
state: Object,
|
||||
repository: Repository
|
||||
state: object,
|
||||
repository: Repository,
|
||||
): boolean {
|
||||
return isPending(state, FETCH_BRANCHES, createKey(repository));
|
||||
}
|
||||
|
||||
export function getFetchBranchesFailure(state: Object, repository: Repository) {
|
||||
export function getFetchBranchesFailure(state: object, repository: Repository) {
|
||||
return getFailure(state, FETCH_BRANCHES, createKey(repository));
|
||||
}
|
||||
|
||||
export function fetchBranchesPending(repository: Repository) {
|
||||
return {
|
||||
type: FETCH_BRANCHES_PENDING,
|
||||
payload: { repository },
|
||||
itemId: createKey(repository)
|
||||
payload: {
|
||||
repository,
|
||||
},
|
||||
itemId: createKey(repository),
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchBranchesSuccess(data: string, repository: Repository) {
|
||||
return {
|
||||
type: FETCH_BRANCHES_SUCCESS,
|
||||
payload: { data, repository },
|
||||
itemId: createKey(repository)
|
||||
payload: {
|
||||
data,
|
||||
repository,
|
||||
},
|
||||
itemId: createKey(repository),
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchBranchesFailure(repository: Repository, error: Error) {
|
||||
return {
|
||||
type: FETCH_BRANCHES_FAILURE,
|
||||
payload: { error, repository },
|
||||
itemId: createKey(repository)
|
||||
payload: {
|
||||
error,
|
||||
repository,
|
||||
},
|
||||
itemId: createKey(repository),
|
||||
};
|
||||
}
|
||||
|
||||
export function isCreateBranchPending(state: Object, repository: Repository) {
|
||||
export function isCreateBranchPending(state: object, repository: Repository) {
|
||||
return isPending(state, CREATE_BRANCH, createKey(repository));
|
||||
}
|
||||
|
||||
export function getCreateBranchFailure(state: Object) {
|
||||
export function getCreateBranchFailure(state: object) {
|
||||
return getFailure(state, CREATE_BRANCH);
|
||||
}
|
||||
|
||||
export function createBranchPending(repository: Repository): Action {
|
||||
return {
|
||||
type: CREATE_BRANCH_PENDING,
|
||||
payload: { repository },
|
||||
itemId: createKey(repository)
|
||||
payload: {
|
||||
repository,
|
||||
},
|
||||
itemId: createKey(repository),
|
||||
};
|
||||
}
|
||||
|
||||
export function createBranchSuccess(repository: Repository): Action {
|
||||
return {
|
||||
type: CREATE_BRANCH_SUCCESS,
|
||||
payload: { repository },
|
||||
itemId: createKey(repository)
|
||||
payload: {
|
||||
repository,
|
||||
},
|
||||
itemId: createKey(repository),
|
||||
};
|
||||
}
|
||||
|
||||
export function createBranchFailure(
|
||||
repository: Repository,
|
||||
error: Error
|
||||
error: Error,
|
||||
): Action {
|
||||
return {
|
||||
type: CREATE_BRANCH_FAILURE,
|
||||
payload: { repository, error },
|
||||
itemId: createKey(repository)
|
||||
payload: {
|
||||
repository,
|
||||
error,
|
||||
},
|
||||
itemId: createKey(repository),
|
||||
};
|
||||
}
|
||||
|
||||
export function createBranchReset(repository: Repository): Action {
|
||||
return {
|
||||
type: CREATE_BRANCH_RESET,
|
||||
payload: { repository },
|
||||
itemId: createKey(repository)
|
||||
payload: {
|
||||
repository,
|
||||
},
|
||||
itemId: createKey(repository),
|
||||
};
|
||||
}
|
||||
|
||||
export function isFetchBranchPending(
|
||||
state: Object,
|
||||
state: object,
|
||||
repository: Repository,
|
||||
name: string
|
||||
name: string,
|
||||
) {
|
||||
return isPending(state, FETCH_BRANCH, createKey(repository) + "/" + name);
|
||||
return isPending(state, FETCH_BRANCH, createKey(repository) + '/' + name);
|
||||
}
|
||||
|
||||
export function getFetchBranchFailure(
|
||||
state: Object,
|
||||
state: object,
|
||||
repository: Repository,
|
||||
name: string
|
||||
name: string,
|
||||
) {
|
||||
return getFailure(state, FETCH_BRANCH, createKey(repository) + "/" + name);
|
||||
return getFailure(state, FETCH_BRANCH, createKey(repository) + '/' + name);
|
||||
}
|
||||
|
||||
export function fetchBranchPending(
|
||||
repository: Repository,
|
||||
name: string
|
||||
name: string,
|
||||
): Action {
|
||||
return {
|
||||
type: FETCH_BRANCH_PENDING,
|
||||
payload: { repository, name },
|
||||
itemId: createKey(repository) + "/" + name
|
||||
payload: {
|
||||
repository,
|
||||
name,
|
||||
},
|
||||
itemId: createKey(repository) + '/' + name,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchBranchSuccess(
|
||||
repository: Repository,
|
||||
branch: Branch
|
||||
branch: Branch,
|
||||
): Action {
|
||||
return {
|
||||
type: FETCH_BRANCH_SUCCESS,
|
||||
payload: { repository, branch },
|
||||
itemId: createKey(repository) + "/" + branch.name
|
||||
payload: {
|
||||
repository,
|
||||
branch,
|
||||
},
|
||||
itemId: createKey(repository) + '/' + branch.name,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchBranchFailure(
|
||||
repository: Repository,
|
||||
name: string,
|
||||
error: Error
|
||||
error: Error,
|
||||
): Action {
|
||||
return {
|
||||
type: FETCH_BRANCH_FAILURE,
|
||||
payload: { error, repository, name },
|
||||
itemId: createKey(repository) + "/" + name
|
||||
payload: {
|
||||
error,
|
||||
repository,
|
||||
name,
|
||||
},
|
||||
itemId: createKey(repository) + '/' + name,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -316,12 +345,12 @@ const reduceByBranchesSuccess = (state, payload) => {
|
||||
return {
|
||||
[key]: {
|
||||
list: response,
|
||||
byName
|
||||
}
|
||||
byName,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
[key]: []
|
||||
[key]: [],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -340,14 +369,16 @@ const reduceByBranchSuccess = (state, payload) => {
|
||||
|
||||
return {
|
||||
...state,
|
||||
[key]: repoState
|
||||
[key]: repoState,
|
||||
};
|
||||
};
|
||||
|
||||
export default function reducer(
|
||||
state: {} = {},
|
||||
action: Action = { type: "UNKNOWN" }
|
||||
): Object {
|
||||
action: Action = {
|
||||
type: 'UNKNOWN',
|
||||
},
|
||||
): object {
|
||||
if (!action.payload) {
|
||||
return state;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import { orderBranches } from "./orderBranches";
|
||||
|
||||
const branch1 = { name: "branch1", revision: "revision1" };
|
||||
const branch2 = { name: "branch2", revision: "revision2" };
|
||||
const branch3 = { name: "branch3", revision: "revision3", defaultBranch: true };
|
||||
const defaultBranch = {
|
||||
name: "default",
|
||||
revision: "revision4",
|
||||
defaultBranch: false
|
||||
};
|
||||
const developBranch = {
|
||||
name: "develop",
|
||||
revision: "revision5",
|
||||
defaultBranch: false
|
||||
};
|
||||
const masterBranch = {
|
||||
name: "master",
|
||||
revision: "revision6",
|
||||
defaultBranch: false
|
||||
};
|
||||
|
||||
describe("order branches", () => {
|
||||
it("should return branches", () => {
|
||||
let branches = [branch1, branch2];
|
||||
orderBranches(branches);
|
||||
expect(branches).toEqual([branch1, branch2]);
|
||||
});
|
||||
|
||||
it("should return defaultBranch first", () => {
|
||||
let branches = [branch1, branch2, branch3];
|
||||
orderBranches(branches);
|
||||
expect(branches).toEqual([branch3, branch1, branch2]);
|
||||
});
|
||||
|
||||
it("should order special branches as follows: master > default > develop", () => {
|
||||
let branches = [defaultBranch, developBranch, masterBranch];
|
||||
orderBranches(branches);
|
||||
expect(branches).toEqual([masterBranch, defaultBranch, developBranch]);
|
||||
});
|
||||
|
||||
it("should order special branches but starting with defaultBranch", () => {
|
||||
let branches = [masterBranch, developBranch, defaultBranch, branch3];
|
||||
orderBranches(branches);
|
||||
expect(branches).toEqual([
|
||||
branch3,
|
||||
masterBranch,
|
||||
defaultBranch,
|
||||
developBranch
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
import { orderBranches } from './orderBranches';
|
||||
|
||||
const branch1 = {
|
||||
name: 'branch1',
|
||||
revision: 'revision1',
|
||||
};
|
||||
const branch2 = {
|
||||
name: 'branch2',
|
||||
revision: 'revision2',
|
||||
};
|
||||
const branch3 = {
|
||||
name: 'branch3',
|
||||
revision: 'revision3',
|
||||
defaultBranch: true,
|
||||
};
|
||||
const defaultBranch = {
|
||||
name: 'default',
|
||||
revision: 'revision4',
|
||||
defaultBranch: false,
|
||||
};
|
||||
const developBranch = {
|
||||
name: 'develop',
|
||||
revision: 'revision5',
|
||||
defaultBranch: false,
|
||||
};
|
||||
const masterBranch = {
|
||||
name: 'master',
|
||||
revision: 'revision6',
|
||||
defaultBranch: false,
|
||||
};
|
||||
|
||||
describe('order branches', () => {
|
||||
it('should return branches', () => {
|
||||
let branches = [branch1, branch2];
|
||||
orderBranches(branches);
|
||||
expect(branches).toEqual([branch1, branch2]);
|
||||
});
|
||||
|
||||
it('should return defaultBranch first', () => {
|
||||
let branches = [branch1, branch2, branch3];
|
||||
orderBranches(branches);
|
||||
expect(branches).toEqual([branch3, branch1, branch2]);
|
||||
});
|
||||
|
||||
it('should order special branches as follows: master > default > develop', () => {
|
||||
let branches = [defaultBranch, developBranch, masterBranch];
|
||||
orderBranches(branches);
|
||||
expect(branches).toEqual([masterBranch, defaultBranch, developBranch]);
|
||||
});
|
||||
|
||||
it('should order special branches but starting with defaultBranch', () => {
|
||||
let branches = [masterBranch, developBranch, defaultBranch, branch3];
|
||||
orderBranches(branches);
|
||||
expect(branches).toEqual([
|
||||
branch3,
|
||||
masterBranch,
|
||||
defaultBranch,
|
||||
developBranch,
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,6 @@
|
||||
// @flow
|
||||
|
||||
// master, default should always be the first one,
|
||||
// followed by develop the rest should be ordered by its name
|
||||
import type {Branch} from "@scm-manager/ui-types";
|
||||
import { Branch } from '@scm-manager/ui-types';
|
||||
|
||||
export function orderBranches(branches: Branch[]) {
|
||||
branches.sort((a, b) => {
|
||||
@@ -10,17 +8,17 @@ export function orderBranches(branches: Branch[]) {
|
||||
return -20;
|
||||
} else if (!a.defaultBranch && b.defaultBranch) {
|
||||
return 20;
|
||||
} else if (a.name === "master" && b.name !== "master") {
|
||||
} else if (a.name === 'master' && b.name !== 'master') {
|
||||
return -10;
|
||||
} else if (a.name !== "master" && b.name === "master") {
|
||||
} else if (a.name !== 'master' && b.name === 'master') {
|
||||
return 10;
|
||||
} else if (a.name === "default" && b.name !== "default") {
|
||||
} else if (a.name === 'default' && b.name !== 'default') {
|
||||
return -10;
|
||||
} else if (a.name !== "default" && b.name === "default") {
|
||||
} else if (a.name !== 'default' && b.name === 'default') {
|
||||
return 10;
|
||||
} else if (a.name === "develop" && b.name !== "develop") {
|
||||
} else if (a.name === 'develop' && b.name !== 'develop') {
|
||||
return -5;
|
||||
} else if (a.name !== "develop" && b.name === "develop") {
|
||||
} else if (a.name !== 'develop' && b.name === 'develop') {
|
||||
return 5;
|
||||
} else if (a.name < b.name) {
|
||||
return -1;
|
||||
@@ -1,28 +0,0 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import { NavLink } from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
editUrl: string,
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class EditRepoNavLink extends React.Component<Props> {
|
||||
isEditable = () => {
|
||||
return this.props.repository._links.update;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { editUrl, t } = this.props;
|
||||
|
||||
if (!this.isEditable()) {
|
||||
return null;
|
||||
}
|
||||
return <NavLink to={editUrl} label={t("repositoryRoot.menu.generalNavLink")} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(EditRepoNavLink);
|
||||
@@ -1,35 +0,0 @@
|
||||
import React from "react";
|
||||
import { shallow, mount } from "@scm-manager/ui-tests/enzyme-router";
|
||||
import "@scm-manager/ui-tests/enzyme";
|
||||
import "@scm-manager/ui-tests/i18n";
|
||||
|
||||
import EditRepoNavLink from "./EditRepoNavLink";
|
||||
|
||||
describe("GeneralNavLink", () => {
|
||||
|
||||
it("should render nothing, if the modify link is missing", () => {
|
||||
const repository = {
|
||||
_links: {}
|
||||
};
|
||||
|
||||
const navLink = shallow(
|
||||
<EditRepoNavLink repository={repository} editUrl="" />
|
||||
);
|
||||
expect(navLink.text()).toBe("");
|
||||
});
|
||||
|
||||
it("should render the navLink", () => {
|
||||
const repository = {
|
||||
_links: {
|
||||
update: {
|
||||
href: "/repositories"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const navLink = mount(
|
||||
<EditRepoNavLink repository={repository} editUrl="" />
|
||||
);
|
||||
expect(navLink.text()).toBe("repositoryRoot.menu.generalNavLink");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import { shallow, mount } from '@scm-manager/ui-tests/enzyme-router';
|
||||
import '@scm-manager/ui-tests/enzyme';
|
||||
import '@scm-manager/ui-tests/i18n';
|
||||
|
||||
import EditRepoNavLink from './EditRepoNavLink';
|
||||
|
||||
describe('GeneralNavLink', () => {
|
||||
it('should render nothing, if the modify link is missing', () => {
|
||||
const repository = {
|
||||
_links: {},
|
||||
};
|
||||
|
||||
const navLink = shallow(
|
||||
<EditRepoNavLink repository={repository} editUrl="" />,
|
||||
);
|
||||
expect(navLink.text()).toBe('');
|
||||
});
|
||||
|
||||
it('should render the navLink', () => {
|
||||
const repository = {
|
||||
_links: {
|
||||
update: {
|
||||
href: '/repositories',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const navLink = mount(
|
||||
<EditRepoNavLink repository={repository} editUrl="" />,
|
||||
);
|
||||
expect(navLink.text()).toBe('repositoryRoot.menu.generalNavLink');
|
||||
});
|
||||
});
|
||||
29
scm-ui/ui-webapp/src/repos/components/EditRepoNavLink.tsx
Normal file
29
scm-ui/ui-webapp/src/repos/components/EditRepoNavLink.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { Repository } from '@scm-manager/ui-types';
|
||||
import { NavLink } from '@scm-manager/ui-components';
|
||||
import { translate } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
repository: Repository;
|
||||
editUrl: string;
|
||||
t: (p: string) => string;
|
||||
};
|
||||
|
||||
class EditRepoNavLink extends React.Component<Props> {
|
||||
isEditable = () => {
|
||||
return this.props.repository._links.update;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { editUrl, t } = this.props;
|
||||
|
||||
if (!this.isEditable()) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<NavLink to={editUrl} label={t('repositoryRoot.menu.generalNavLink')} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate('repos')(EditRepoNavLink);
|
||||
@@ -1,28 +0,0 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { NavLink } from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
|
||||
type Props = {
|
||||
permissionUrl: string,
|
||||
t: string => string,
|
||||
repository: Repository
|
||||
};
|
||||
|
||||
class PermissionsNavLink extends React.Component<Props> {
|
||||
hasPermissionsLink = () => {
|
||||
return this.props.repository._links.permissions;
|
||||
};
|
||||
render() {
|
||||
if (!this.hasPermissionsLink()) {
|
||||
return null;
|
||||
}
|
||||
const { permissionUrl, t } = this.props;
|
||||
return (
|
||||
<NavLink to={permissionUrl} label={t("repositoryRoot.menu.permissionsNavLink")} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(PermissionsNavLink);
|
||||
@@ -1,35 +0,0 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import {mount, shallow } from "@scm-manager/ui-tests/enzyme-router";
|
||||
import "@scm-manager/ui-tests/enzyme";
|
||||
import "@scm-manager/ui-tests/i18n";
|
||||
import PermissionsNavLink from "./PermissionsNavLink";
|
||||
|
||||
describe("PermissionsNavLink", () => {
|
||||
|
||||
it("should render nothing, if the modify link is missing", () => {
|
||||
const repository = {
|
||||
_links: {}
|
||||
};
|
||||
|
||||
const navLink = shallow(
|
||||
<PermissionsNavLink repository={repository} permissionUrl="" />
|
||||
);
|
||||
expect(navLink.text()).toBe("");
|
||||
});
|
||||
|
||||
it("should render the navLink", () => {
|
||||
const repository = {
|
||||
_links: {
|
||||
permissions: {
|
||||
href: "/permissions"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const navLink = mount(
|
||||
<PermissionsNavLink repository={repository} permissionUrl="" />
|
||||
);
|
||||
expect(navLink.text()).toBe("repositoryRoot.menu.permissionsNavLink");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import { mount, shallow } from '@scm-manager/ui-tests/enzyme-router';
|
||||
import '@scm-manager/ui-tests/enzyme';
|
||||
import '@scm-manager/ui-tests/i18n';
|
||||
import PermissionsNavLink from './PermissionsNavLink';
|
||||
|
||||
describe('PermissionsNavLink', () => {
|
||||
it('should render nothing, if the modify link is missing', () => {
|
||||
const repository = {
|
||||
_links: {},
|
||||
};
|
||||
|
||||
const navLink = shallow(
|
||||
<PermissionsNavLink repository={repository} permissionUrl="" />,
|
||||
);
|
||||
expect(navLink.text()).toBe('');
|
||||
});
|
||||
|
||||
it('should render the navLink', () => {
|
||||
const repository = {
|
||||
_links: {
|
||||
permissions: {
|
||||
href: '/permissions',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const navLink = mount(
|
||||
<PermissionsNavLink repository={repository} permissionUrl="" />,
|
||||
);
|
||||
expect(navLink.text()).toBe('repositoryRoot.menu.permissionsNavLink');
|
||||
});
|
||||
});
|
||||
30
scm-ui/ui-webapp/src/repos/components/PermissionsNavLink.tsx
Normal file
30
scm-ui/ui-webapp/src/repos/components/PermissionsNavLink.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import { NavLink } from '@scm-manager/ui-components';
|
||||
import { translate } from 'react-i18next';
|
||||
import { Repository } from '@scm-manager/ui-types';
|
||||
|
||||
type Props = {
|
||||
permissionUrl: string;
|
||||
t: (p: string) => string;
|
||||
repository: Repository;
|
||||
};
|
||||
|
||||
class PermissionsNavLink extends React.Component<Props> {
|
||||
hasPermissionsLink = () => {
|
||||
return this.props.repository._links.permissions;
|
||||
};
|
||||
render() {
|
||||
if (!this.hasPermissionsLink()) {
|
||||
return null;
|
||||
}
|
||||
const { permissionUrl, t } = this.props;
|
||||
return (
|
||||
<NavLink
|
||||
to={permissionUrl}
|
||||
label={t('repositoryRoot.menu.permissionsNavLink')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate('repos')(PermissionsNavLink);
|
||||
@@ -1,13 +1,12 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import { MailLink, DateFromNow } from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
import React from 'react';
|
||||
import { Repository } from '@scm-manager/ui-types';
|
||||
import { MailLink, DateFromNow } from '@scm-manager/ui-components';
|
||||
import { translate } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
repository: Repository;
|
||||
// context props
|
||||
t: string => string
|
||||
t: (p: string) => string;
|
||||
};
|
||||
|
||||
class RepositoryDetailTable extends React.Component<Props> {
|
||||
@@ -17,31 +16,31 @@ class RepositoryDetailTable extends React.Component<Props> {
|
||||
<table className="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{t("repository.name")}</th>
|
||||
<th>{t('repository.name')}</th>
|
||||
<td>{repository.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t("repository.type")}</th>
|
||||
<th>{t('repository.type')}</th>
|
||||
<td>{repository.type}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t("repository.contact")}</th>
|
||||
<th>{t('repository.contact')}</th>
|
||||
<td>
|
||||
<MailLink address={repository.contact} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t("repository.description")}</th>
|
||||
<th>{t('repository.description')}</th>
|
||||
<td>{repository.description}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t("repository.creationDate")}</th>
|
||||
<th>{t('repository.creationDate')}</th>
|
||||
<td>
|
||||
<DateFromNow date={repository.creationDate} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t("repository.lastModified")}</th>
|
||||
<th>{t('repository.lastModified')}</th>
|
||||
<td>
|
||||
<DateFromNow date={repository.lastModified} />
|
||||
</td>
|
||||
@@ -52,4 +51,4 @@ class RepositoryDetailTable extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(RepositoryDetailTable);
|
||||
export default translate('repos')(RepositoryDetailTable);
|
||||
@@ -1,11 +1,10 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import RepositoryDetailTable from "./RepositoryDetailTable";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import React from 'react';
|
||||
import { Repository } from '@scm-manager/ui-types';
|
||||
import RepositoryDetailTable from './RepositoryDetailTable';
|
||||
import { ExtensionPoint } from '@scm-manager/ui-extensions';
|
||||
|
||||
type Props = {
|
||||
repository: Repository
|
||||
repository: Repository;
|
||||
};
|
||||
|
||||
class RepositoryDetails extends React.Component<Props> {
|
||||
@@ -13,13 +12,15 @@ class RepositoryDetails extends React.Component<Props> {
|
||||
const { repository } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<RepositoryDetailTable repository={repository}/>
|
||||
<hr/>
|
||||
<RepositoryDetailTable repository={repository} />
|
||||
<hr />
|
||||
<div className="content">
|
||||
<ExtensionPoint
|
||||
name="repos.repository-details.information"
|
||||
renderAll={true}
|
||||
props={{ repository }}
|
||||
props={{
|
||||
repository,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,52 +0,0 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { shallow, mount } from "@scm-manager/ui-tests/enzyme-router";
|
||||
import "@scm-manager/ui-tests/i18n";
|
||||
import RepositoryNavLink from "./RepositoryNavLink";
|
||||
|
||||
describe("RepositoryNavLink", () => {
|
||||
|
||||
it("should render nothing, if the sources link is missing", () => {
|
||||
const repository = {
|
||||
namespace: "Namespace",
|
||||
name: "Repo",
|
||||
type: "GIT",
|
||||
_links: {}
|
||||
};
|
||||
|
||||
const navLink = shallow(
|
||||
<RepositoryNavLink
|
||||
repository={repository}
|
||||
linkName="sources"
|
||||
to="/sources"
|
||||
label="Sources"
|
||||
activeOnlyWhenExact={true}
|
||||
/>
|
||||
);
|
||||
expect(navLink.text()).toBe("");
|
||||
});
|
||||
|
||||
it("should render the navLink", () => {
|
||||
const repository = {
|
||||
namespace: "Namespace",
|
||||
name: "Repo",
|
||||
type: "GIT",
|
||||
_links: {
|
||||
sources: {
|
||||
href: "/sources"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const navLink = mount(
|
||||
<RepositoryNavLink
|
||||
repository={repository}
|
||||
linkName="sources"
|
||||
to="/sources"
|
||||
label="Sources"
|
||||
activeOnlyWhenExact={true}
|
||||
/>
|
||||
);
|
||||
expect(navLink.text()).toBe("Sources");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
import { shallow, mount } from '@scm-manager/ui-tests/enzyme-router';
|
||||
import '@scm-manager/ui-tests/i18n';
|
||||
import RepositoryNavLink from './RepositoryNavLink';
|
||||
|
||||
describe('RepositoryNavLink', () => {
|
||||
it('should render nothing, if the sources link is missing', () => {
|
||||
const repository = {
|
||||
namespace: 'Namespace',
|
||||
name: 'Repo',
|
||||
type: 'GIT',
|
||||
_links: {},
|
||||
};
|
||||
|
||||
const navLink = shallow(
|
||||
<RepositoryNavLink
|
||||
repository={repository}
|
||||
linkName="sources"
|
||||
to="/sources"
|
||||
label="Sources"
|
||||
activeOnlyWhenExact={true}
|
||||
/>,
|
||||
);
|
||||
expect(navLink.text()).toBe('');
|
||||
});
|
||||
|
||||
it('should render the navLink', () => {
|
||||
const repository = {
|
||||
namespace: 'Namespace',
|
||||
name: 'Repo',
|
||||
type: 'GIT',
|
||||
_links: {
|
||||
sources: {
|
||||
href: '/sources',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const navLink = mount(
|
||||
<RepositoryNavLink
|
||||
repository={repository}
|
||||
linkName="sources"
|
||||
to="/sources"
|
||||
label="Sources"
|
||||
activeOnlyWhenExact={true}
|
||||
/>,
|
||||
);
|
||||
expect(navLink.text()).toBe('Sources');
|
||||
});
|
||||
});
|
||||
@@ -1,15 +1,14 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import { NavLink } from "@scm-manager/ui-components";
|
||||
import React from 'react';
|
||||
import { Repository } from '@scm-manager/ui-types';
|
||||
import { NavLink } from '@scm-manager/ui-components';
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
to: string,
|
||||
label: string,
|
||||
linkName: string,
|
||||
activeWhenMatch?: (route: any) => boolean,
|
||||
activeOnlyWhenExact: boolean
|
||||
repository: Repository;
|
||||
to: string;
|
||||
label: string;
|
||||
linkName: string;
|
||||
activeWhenMatch?: (route: any) => boolean;
|
||||
activeOnlyWhenExact: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1,10 +1,9 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { Interpolate, translate } from "react-i18next";
|
||||
import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import type { Changeset, Repository, Tag } from "@scm-manager/ui-types";
|
||||
import React from 'react';
|
||||
import { Interpolate, translate } from 'react-i18next';
|
||||
import classNames from 'classnames';
|
||||
import styled from 'styled-components';
|
||||
import { ExtensionPoint } from '@scm-manager/ui-extensions';
|
||||
import { Changeset, Repository, Tag } from '@scm-manager/ui-types';
|
||||
import {
|
||||
DateFromNow,
|
||||
ChangesetId,
|
||||
@@ -15,19 +14,19 @@ import {
|
||||
AvatarImage,
|
||||
changesets,
|
||||
Level,
|
||||
Button
|
||||
} from "@scm-manager/ui-components";
|
||||
Button,
|
||||
} from '@scm-manager/ui-components';
|
||||
|
||||
type Props = {
|
||||
changeset: Changeset,
|
||||
repository: Repository,
|
||||
changeset: Changeset;
|
||||
repository: Repository;
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
t: (p: string) => string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
collapsed: boolean
|
||||
collapsed: boolean;
|
||||
};
|
||||
|
||||
const RightMarginP = styled.p`
|
||||
@@ -48,7 +47,7 @@ class ChangesetDetails extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
collapsed: false
|
||||
collapsed: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -64,11 +63,14 @@ class ChangesetDetails extends React.Component<Props, State> {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classNames("content", "is-marginless")}>
|
||||
<div className={classNames('content', 'is-marginless')}>
|
||||
<h4>
|
||||
<ExtensionPoint
|
||||
name="changeset.description"
|
||||
props={{ changeset, value: description.title }}
|
||||
props={{
|
||||
changeset,
|
||||
value: description.title,
|
||||
}}
|
||||
renderAll={false}
|
||||
>
|
||||
{description.title}
|
||||
@@ -76,7 +78,7 @@ class ChangesetDetails extends React.Component<Props, State> {
|
||||
</h4>
|
||||
<article className="media">
|
||||
<AvatarWrapper>
|
||||
<RightMarginP className={classNames("image", "is-64x64")}>
|
||||
<RightMarginP className={classNames('image', 'is-64x64')}>
|
||||
<AvatarImage person={changeset.author} />
|
||||
</RightMarginP>
|
||||
</AvatarWrapper>
|
||||
@@ -92,12 +94,15 @@ class ChangesetDetails extends React.Component<Props, State> {
|
||||
</article>
|
||||
|
||||
<p>
|
||||
{description.message.split("\n").map((item, key) => {
|
||||
{description.message.split('\n').map((item, key) => {
|
||||
return (
|
||||
<span key={key}>
|
||||
<ExtensionPoint
|
||||
name="changeset.description"
|
||||
props={{ changeset, value: item }}
|
||||
props={{
|
||||
changeset,
|
||||
value: item,
|
||||
}}
|
||||
renderAll={false}
|
||||
>
|
||||
{item}
|
||||
@@ -114,8 +119,8 @@ class ChangesetDetails extends React.Component<Props, State> {
|
||||
<Button
|
||||
action={this.collapseDiffs}
|
||||
color="default"
|
||||
icon={collapsed ? "eye" : "eye-slash"}
|
||||
label={t("changesets.collapseDiffs")}
|
||||
icon={collapsed ? 'eye' : 'eye-slash'}
|
||||
label={t('changesets.collapseDiffs')}
|
||||
reducedMobile={true}
|
||||
/>
|
||||
}
|
||||
@@ -146,9 +151,9 @@ class ChangesetDetails extends React.Component<Props, State> {
|
||||
|
||||
collapseDiffs = () => {
|
||||
this.setState(state => ({
|
||||
collapsed: !state.collapsed
|
||||
collapsed: !state.collapsed,
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("repos")(ChangesetDetails);
|
||||
export default translate('repos')(ChangesetDetails);
|
||||
@@ -1,34 +1,33 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import React from 'react';
|
||||
import { translate } from 'react-i18next';
|
||||
import {
|
||||
Subtitle,
|
||||
InputField,
|
||||
Select,
|
||||
SubmitButton,
|
||||
Textarea
|
||||
} from "@scm-manager/ui-components";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import type { Repository, RepositoryType } from "@scm-manager/ui-types";
|
||||
import * as validator from "./repositoryValidation";
|
||||
Textarea,
|
||||
} from '@scm-manager/ui-components';
|
||||
import { ExtensionPoint } from '@scm-manager/ui-extensions';
|
||||
import { Repository, RepositoryType } from '@scm-manager/ui-types';
|
||||
import * as validator from './repositoryValidation';
|
||||
|
||||
type Props = {
|
||||
submitForm: Repository => void,
|
||||
repository?: Repository,
|
||||
repositoryTypes: RepositoryType[],
|
||||
namespaceStrategy: string,
|
||||
loading?: boolean,
|
||||
t: string => string
|
||||
submitForm: (p: Repository) => void;
|
||||
repository?: Repository;
|
||||
repositoryTypes: RepositoryType[];
|
||||
namespaceStrategy: string;
|
||||
loading?: boolean;
|
||||
t: (p: string) => string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
repository: Repository,
|
||||
namespaceValidationError: boolean,
|
||||
nameValidationError: boolean,
|
||||
contactValidationError: boolean
|
||||
repository: Repository;
|
||||
namespaceValidationError: boolean;
|
||||
nameValidationError: boolean;
|
||||
contactValidationError: boolean;
|
||||
};
|
||||
|
||||
const CUSTOM_NAMESPACE_STRATEGY = "CustomNamespaceStrategy";
|
||||
const CUSTOM_NAMESPACE_STRATEGY = 'CustomNamespaceStrategy';
|
||||
|
||||
class RepositoryForm extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
@@ -36,23 +35,27 @@ class RepositoryForm extends React.Component<Props, State> {
|
||||
|
||||
this.state = {
|
||||
repository: {
|
||||
name: "",
|
||||
namespace: "",
|
||||
type: "",
|
||||
contact: "",
|
||||
description: "",
|
||||
_links: {}
|
||||
name: '',
|
||||
namespace: '',
|
||||
type: '',
|
||||
contact: '',
|
||||
description: '',
|
||||
_links: {},
|
||||
},
|
||||
namespaceValidationError: false,
|
||||
nameValidationError: false,
|
||||
contactValidationError: false
|
||||
contactValidationError: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { repository } = this.props;
|
||||
if (repository) {
|
||||
this.setState({ repository: { ...repository } });
|
||||
this.setState({
|
||||
repository: {
|
||||
...repository,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,14 +104,14 @@ class RepositoryForm extends React.Component<Props, State> {
|
||||
<SubmitButton
|
||||
disabled={!this.isValid()}
|
||||
loading={loading}
|
||||
label={t("repositoryForm.submit")}
|
||||
label={t('repositoryForm.submit')}
|
||||
/>
|
||||
);
|
||||
|
||||
let subtitle = null;
|
||||
if (this.props.repository) {
|
||||
// edit existing repo
|
||||
subtitle = <Subtitle subtitle={t("repositoryForm.subtitle")} />;
|
||||
subtitle = <Subtitle subtitle={t('repositoryForm.subtitle')} />;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -117,20 +120,20 @@ class RepositoryForm extends React.Component<Props, State> {
|
||||
<form onSubmit={this.submit}>
|
||||
{this.renderCreateOnlyFields()}
|
||||
<InputField
|
||||
label={t("repository.contact")}
|
||||
label={t('repository.contact')}
|
||||
onChange={this.handleContactChange}
|
||||
value={repository ? repository.contact : ""}
|
||||
value={repository ? repository.contact : ''}
|
||||
validationError={this.state.contactValidationError}
|
||||
errorMessage={t("validation.contact-invalid")}
|
||||
helpText={t("help.contactHelpText")}
|
||||
errorMessage={t('validation.contact-invalid')}
|
||||
helpText={t('help.contactHelpText')}
|
||||
disabled={disabled}
|
||||
/>
|
||||
|
||||
<Textarea
|
||||
label={t("repository.description")}
|
||||
label={t('repository.description')}
|
||||
onChange={this.handleDescriptionChange}
|
||||
value={repository ? repository.description : ""}
|
||||
helpText={t("help.descriptionHelpText")}
|
||||
value={repository ? repository.description : ''}
|
||||
helpText={t('help.descriptionHelpText')}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{submitButton}
|
||||
@@ -143,7 +146,7 @@ class RepositoryForm extends React.Component<Props, State> {
|
||||
return repositoryTypes.map(repositoryType => {
|
||||
return {
|
||||
label: repositoryType.displayName,
|
||||
value: repositoryType.name
|
||||
value: repositoryType.name,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -152,12 +155,12 @@ class RepositoryForm extends React.Component<Props, State> {
|
||||
const { namespaceStrategy, t } = this.props;
|
||||
const repository = this.state.repository;
|
||||
const props = {
|
||||
label: t("repository.namespace"),
|
||||
helpText: t("help.namespaceHelpText"),
|
||||
value: repository ? repository.namespace : "",
|
||||
label: t('repository.namespace'),
|
||||
helpText: t('help.namespaceHelpText'),
|
||||
value: repository ? repository.namespace : '',
|
||||
onChange: this.handleNamespaceChange,
|
||||
errorMessage: t("validation.namespace-invalid"),
|
||||
validationError: this.state.namespaceValidationError
|
||||
errorMessage: t('validation.namespace-invalid'),
|
||||
validationError: this.state.namespaceValidationError,
|
||||
};
|
||||
|
||||
if (namespaceStrategy === CUSTOM_NAMESPACE_STRATEGY) {
|
||||
@@ -183,19 +186,19 @@ class RepositoryForm extends React.Component<Props, State> {
|
||||
<>
|
||||
{this.renderNamespaceField()}
|
||||
<InputField
|
||||
label={t("repository.name")}
|
||||
label={t('repository.name')}
|
||||
onChange={this.handleNameChange}
|
||||
value={repository ? repository.name : ""}
|
||||
value={repository ? repository.name : ''}
|
||||
validationError={this.state.nameValidationError}
|
||||
errorMessage={t("validation.name-invalid")}
|
||||
helpText={t("help.nameHelpText")}
|
||||
errorMessage={t('validation.name-invalid')}
|
||||
helpText={t('help.nameHelpText')}
|
||||
/>
|
||||
<Select
|
||||
label={t("repository.type")}
|
||||
label={t('repository.type')}
|
||||
onChange={this.handleTypeChange}
|
||||
value={repository ? repository.type : ""}
|
||||
value={repository ? repository.type : ''}
|
||||
options={this.createSelectOptions(repositoryTypes)}
|
||||
helpText={t("help.typeHelpText")}
|
||||
helpText={t('help.typeHelpText')}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -204,35 +207,50 @@ class RepositoryForm extends React.Component<Props, State> {
|
||||
handleNamespaceChange = (namespace: string) => {
|
||||
this.setState({
|
||||
namespaceValidationError: !validator.isNameValid(namespace),
|
||||
repository: { ...this.state.repository, namespace }
|
||||
repository: {
|
||||
...this.state.repository,
|
||||
namespace,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
handleNameChange = (name: string) => {
|
||||
this.setState({
|
||||
nameValidationError: !validator.isNameValid(name),
|
||||
repository: { ...this.state.repository, name }
|
||||
repository: {
|
||||
...this.state.repository,
|
||||
name,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
handleTypeChange = (type: string) => {
|
||||
this.setState({
|
||||
repository: { ...this.state.repository, type }
|
||||
repository: {
|
||||
...this.state.repository,
|
||||
type,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
handleContactChange = (contact: string) => {
|
||||
this.setState({
|
||||
contactValidationError: !validator.isContactValid(contact),
|
||||
repository: { ...this.state.repository, contact }
|
||||
repository: {
|
||||
...this.state.repository,
|
||||
contact,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
handleDescriptionChange = (description: string) => {
|
||||
this.setState({
|
||||
repository: { ...this.state.repository, description }
|
||||
repository: {
|
||||
...this.state.repository,
|
||||
description,
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("repos")(RepositoryForm);
|
||||
export default translate('repos')(RepositoryForm);
|
||||
@@ -1,2 +0,0 @@
|
||||
import RepositoryForm from "./RepositoryForm";
|
||||
export default RepositoryForm;
|
||||
2
scm-ui/ui-webapp/src/repos/components/form/index.ts
Normal file
2
scm-ui/ui-webapp/src/repos/components/form/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import RepositoryForm from './RepositoryForm';
|
||||
export default RepositoryForm;
|
||||
@@ -1,106 +0,0 @@
|
||||
import * as validator from "./repositoryValidation";
|
||||
|
||||
describe("repository name validation", () => {
|
||||
// we don't need rich tests, because they are in validation.test.js
|
||||
it("should validate the name", () => {
|
||||
expect(validator.isNameValid("scm-manager")).toBe(true);
|
||||
});
|
||||
|
||||
it("should fail for old nested repository names", () => {
|
||||
// in v2 this is not allowed
|
||||
expect(validator.isNameValid("scm/manager")).toBe(false);
|
||||
expect(validator.isNameValid("scm/ma/nager")).toBe(false);
|
||||
});
|
||||
|
||||
it("should allow same names as the backend", () => {
|
||||
const validPaths = [
|
||||
"scm",
|
||||
"s",
|
||||
"sc",
|
||||
".hiddenrepo",
|
||||
"b.",
|
||||
"...",
|
||||
"..c",
|
||||
"d..",
|
||||
"a..c"
|
||||
];
|
||||
|
||||
validPaths.forEach((path) =>
|
||||
expect(validator.isNameValid(path)).toBe(true)
|
||||
);
|
||||
});
|
||||
|
||||
it("should deny same names as the backend", () => {
|
||||
const invalidPaths = [
|
||||
".",
|
||||
"/",
|
||||
"//",
|
||||
"..",
|
||||
"/.",
|
||||
"/..",
|
||||
"./",
|
||||
"../",
|
||||
"/../",
|
||||
"/./",
|
||||
"/...",
|
||||
"/abc",
|
||||
".../",
|
||||
"/sdf/",
|
||||
"asdf/",
|
||||
"./b",
|
||||
"scm/plugins/.",
|
||||
"scm/../plugins",
|
||||
"scm/main/",
|
||||
"/scm/main/",
|
||||
"scm/./main",
|
||||
"scm//main",
|
||||
"scm\\main",
|
||||
"scm/main-$HOME",
|
||||
"scm/main-${HOME}-home",
|
||||
"scm/main-%HOME-home",
|
||||
"scm/main-%HOME%-home",
|
||||
"abc$abc",
|
||||
"abc%abc",
|
||||
"abc<abc",
|
||||
"abc>abc",
|
||||
"abc#abc",
|
||||
"abc+abc",
|
||||
"abc{abc",
|
||||
"abc}abc",
|
||||
"abc(abc",
|
||||
"abc)abc",
|
||||
"abc[abc",
|
||||
"abc]abc",
|
||||
"abc|abc",
|
||||
"scm/main",
|
||||
"scm/plugins/git-plugin",
|
||||
".scm/plugins",
|
||||
"a/b..",
|
||||
"a/..b",
|
||||
"scm/main",
|
||||
"scm/plugins/git-plugin",
|
||||
"scm/plugins/git-plugin"
|
||||
];
|
||||
|
||||
invalidPaths.forEach((path) =>
|
||||
expect(validator.isNameValid(path)).toBe(false)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("repository contact validation", () => {
|
||||
it("should allow empty contact", () => {
|
||||
expect(validator.isContactValid("")).toBe(true);
|
||||
});
|
||||
|
||||
// we don't need rich tests, because they are in validation.test.js
|
||||
it("should allow real mail addresses", () => {
|
||||
expect(validator.isContactValid("trici.mcmillian@hitchhiker.com")).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it("should fail on invalid mail addresses", () => {
|
||||
expect(validator.isContactValid("tricia")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,104 @@
|
||||
import * as validator from './repositoryValidation';
|
||||
|
||||
describe('repository name validation', () => {
|
||||
// we don't need rich tests, because they are in validation.test.js
|
||||
it('should validate the name', () => {
|
||||
expect(validator.isNameValid('scm-manager')).toBe(true);
|
||||
});
|
||||
|
||||
it('should fail for old nested repository names', () => {
|
||||
// in v2 this is not allowed
|
||||
expect(validator.isNameValid('scm/manager')).toBe(false);
|
||||
expect(validator.isNameValid('scm/ma/nager')).toBe(false);
|
||||
});
|
||||
|
||||
it('should allow same names as the backend', () => {
|
||||
const validPaths = [
|
||||
'scm',
|
||||
's',
|
||||
'sc',
|
||||
'.hiddenrepo',
|
||||
'b.',
|
||||
'...',
|
||||
'..c',
|
||||
'd..',
|
||||
'a..c',
|
||||
];
|
||||
|
||||
validPaths.forEach(path => expect(validator.isNameValid(path)).toBe(true));
|
||||
});
|
||||
|
||||
it('should deny same names as the backend', () => {
|
||||
const invalidPaths = [
|
||||
'.',
|
||||
'/',
|
||||
'//',
|
||||
'..',
|
||||
'/.',
|
||||
'/..',
|
||||
'./',
|
||||
'../',
|
||||
'/../',
|
||||
'/./',
|
||||
'/...',
|
||||
'/abc',
|
||||
'.../',
|
||||
'/sdf/',
|
||||
'asdf/',
|
||||
'./b',
|
||||
'scm/plugins/.',
|
||||
'scm/../plugins',
|
||||
'scm/main/',
|
||||
'/scm/main/',
|
||||
'scm/./main',
|
||||
'scm//main',
|
||||
'scm\\main',
|
||||
'scm/main-$HOME',
|
||||
'scm/main-${HOME}-home',
|
||||
'scm/main-%HOME-home',
|
||||
'scm/main-%HOME%-home',
|
||||
'abc$abc',
|
||||
'abc%abc',
|
||||
'abc<abc',
|
||||
'abc>abc',
|
||||
'abc#abc',
|
||||
'abc+abc',
|
||||
'abc{abc',
|
||||
'abc}abc',
|
||||
'abc(abc',
|
||||
'abc)abc',
|
||||
'abc[abc',
|
||||
'abc]abc',
|
||||
'abc|abc',
|
||||
'scm/main',
|
||||
'scm/plugins/git-plugin',
|
||||
'.scm/plugins',
|
||||
'a/b..',
|
||||
'a/..b',
|
||||
'scm/main',
|
||||
'scm/plugins/git-plugin',
|
||||
'scm/plugins/git-plugin',
|
||||
];
|
||||
|
||||
invalidPaths.forEach(path =>
|
||||
expect(validator.isNameValid(path)).toBe(false),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('repository contact validation', () => {
|
||||
it('should allow empty contact', () => {
|
||||
expect(validator.isContactValid('')).toBe(true);
|
||||
});
|
||||
|
||||
// we don't need rich tests, because they are in validation.test.js
|
||||
it('should allow real mail addresses', () => {
|
||||
expect(validator.isContactValid('trici.mcmillian@hitchhiker.com')).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail on invalid mail addresses', () => {
|
||||
expect(validator.isContactValid('tricia')).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,4 @@
|
||||
// @flow
|
||||
import { validation } from "@scm-manager/ui-components";
|
||||
import { validation } from '@scm-manager/ui-components';
|
||||
|
||||
const nameRegex = /(?!^\.\.$)(?!^\.$)(?!.*[\\\[\]])^[A-Za-z0-9\.][A-Za-z0-9\.\-_]*$/;
|
||||
|
||||
@@ -8,5 +7,5 @@ export const isNameValid = (name: string) => {
|
||||
};
|
||||
|
||||
export function isContactValid(mail: string) {
|
||||
return "" === mail || validation.isMailValid(mail);
|
||||
return '' === mail || validation.isMailValid(mail);
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import { Image } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
repository: Repository
|
||||
};
|
||||
|
||||
class RepositoryAvatar extends React.Component<Props> {
|
||||
render() {
|
||||
const { repository } = this.props;
|
||||
return (
|
||||
<p className="image is-64x64">
|
||||
<ExtensionPoint name="repos.repository-avatar" props={{ repository }}>
|
||||
<Image src="/images/blib.jpg" alt="Logo" />
|
||||
</ExtensionPoint>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RepositoryAvatar;
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { ExtensionPoint } from '@scm-manager/ui-extensions';
|
||||
import { Repository } from '@scm-manager/ui-types';
|
||||
import { Image } from '@scm-manager/ui-components';
|
||||
|
||||
type Props = {
|
||||
repository: Repository;
|
||||
};
|
||||
|
||||
class RepositoryAvatar extends React.Component<Props> {
|
||||
render() {
|
||||
const { repository } = this.props;
|
||||
return (
|
||||
<p className="image is-64x64">
|
||||
<ExtensionPoint
|
||||
name="repos.repository-avatar"
|
||||
props={{
|
||||
repository,
|
||||
}}
|
||||
>
|
||||
<Image src="/images/blib.jpg" alt="Logo" />
|
||||
</ExtensionPoint>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RepositoryAvatar;
|
||||
@@ -1,12 +1,11 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import { CardColumn, DateFromNow } from "@scm-manager/ui-components";
|
||||
import RepositoryEntryLink from "./RepositoryEntryLink";
|
||||
import RepositoryAvatar from "./RepositoryAvatar";
|
||||
import React from 'react';
|
||||
import { Repository } from '@scm-manager/ui-types';
|
||||
import { CardColumn, DateFromNow } from '@scm-manager/ui-components';
|
||||
import RepositoryEntryLink from './RepositoryEntryLink';
|
||||
import RepositoryAvatar from './RepositoryAvatar';
|
||||
|
||||
type Props = {
|
||||
repository: Repository
|
||||
repository: Repository;
|
||||
};
|
||||
|
||||
class RepositoryEntry extends React.Component<Props> {
|
||||
@@ -15,11 +14,11 @@ class RepositoryEntry extends React.Component<Props> {
|
||||
};
|
||||
|
||||
renderBranchesLink = (repository: Repository, repositoryLink: string) => {
|
||||
if (repository._links["branches"]) {
|
||||
if (repository._links['branches']) {
|
||||
return (
|
||||
<RepositoryEntryLink
|
||||
icon="code-branch"
|
||||
to={repositoryLink + "/branches"}
|
||||
to={repositoryLink + '/branches'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -27,11 +26,11 @@ class RepositoryEntry extends React.Component<Props> {
|
||||
};
|
||||
|
||||
renderChangesetsLink = (repository: Repository, repositoryLink: string) => {
|
||||
if (repository._links["changesets"]) {
|
||||
if (repository._links['changesets']) {
|
||||
return (
|
||||
<RepositoryEntryLink
|
||||
icon="exchange-alt"
|
||||
to={repositoryLink + "/changesets"}
|
||||
to={repositoryLink + '/changesets'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -39,20 +38,20 @@ class RepositoryEntry extends React.Component<Props> {
|
||||
};
|
||||
|
||||
renderSourcesLink = (repository: Repository, repositoryLink: string) => {
|
||||
if (repository._links["sources"]) {
|
||||
if (repository._links['sources']) {
|
||||
return (
|
||||
<RepositoryEntryLink icon="code" to={repositoryLink + "/sources"} />
|
||||
<RepositoryEntryLink icon="code" to={repositoryLink + '/sources'} />
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
renderModifyLink = (repository: Repository, repositoryLink: string) => {
|
||||
if (repository._links["update"]) {
|
||||
if (repository._links['update']) {
|
||||
return (
|
||||
<RepositoryEntryLink
|
||||
icon="cog"
|
||||
to={repositoryLink + "/settings/general"}
|
||||
to={repositoryLink + '/settings/general'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
import { Icon } from "@scm-manager/ui-components";
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { Icon } from '@scm-manager/ui-components';
|
||||
|
||||
type Props = {
|
||||
to: string,
|
||||
icon: string
|
||||
to: string;
|
||||
icon: string;
|
||||
};
|
||||
|
||||
const PointerEventsLink = styled(Link)`
|
||||
@@ -1,11 +1,10 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { CardColumnGroup } from "@scm-manager/ui-components";
|
||||
import type { RepositoryGroup } from "@scm-manager/ui-types";
|
||||
import RepositoryEntry from "./RepositoryEntry";
|
||||
import React from 'react';
|
||||
import { CardColumnGroup } from '@scm-manager/ui-components';
|
||||
import { RepositoryGroup } from '@scm-manager/ui-types';
|
||||
import RepositoryEntry from './RepositoryEntry';
|
||||
|
||||
type Props = {
|
||||
group: RepositoryGroup
|
||||
group: RepositoryGroup;
|
||||
};
|
||||
|
||||
class RepositoryGroupEntry extends React.Component<Props> {
|
||||
@@ -1,13 +1,12 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import { Repository } from '@scm-manager/ui-types';
|
||||
|
||||
import groupByNamespace from "./groupByNamespace";
|
||||
import RepositoryGroupEntry from "./RepositoryGroupEntry";
|
||||
import groupByNamespace from './groupByNamespace';
|
||||
import RepositoryGroupEntry from './RepositoryGroupEntry';
|
||||
|
||||
type Props = {
|
||||
repositories: Repository[]
|
||||
repositories: Repository[];
|
||||
};
|
||||
|
||||
class RepositoryList extends React.Component<Props> {
|
||||
@@ -1,74 +0,0 @@
|
||||
// @flow
|
||||
import groupByNamespace from "./groupByNamespace";
|
||||
|
||||
const base = {
|
||||
type: "git",
|
||||
_links: {}
|
||||
};
|
||||
|
||||
const slartiBlueprintsFjords = {
|
||||
...base,
|
||||
namespace: "slarti",
|
||||
name: "fjords-blueprints"
|
||||
};
|
||||
|
||||
const slartiFjords = {
|
||||
...base,
|
||||
namespace: "slarti",
|
||||
name: "fjords"
|
||||
};
|
||||
|
||||
const hitchhikerRestand = {
|
||||
...base,
|
||||
namespace: "hitchhiker",
|
||||
name: "restand"
|
||||
};
|
||||
const hitchhikerPuzzle42 = {
|
||||
...base,
|
||||
namespace: "hitchhiker",
|
||||
name: "puzzle42"
|
||||
};
|
||||
|
||||
const hitchhikerHeartOfGold = {
|
||||
...base,
|
||||
namespace: "hitchhiker",
|
||||
name: "heartOfGold"
|
||||
};
|
||||
|
||||
const zaphodMarvinFirmware = {
|
||||
...base,
|
||||
namespace: "zaphod",
|
||||
name: "marvin-firmware"
|
||||
};
|
||||
|
||||
it("should group the repositories by their namespace", () => {
|
||||
const repositories = [
|
||||
zaphodMarvinFirmware,
|
||||
slartiBlueprintsFjords,
|
||||
hitchhikerRestand,
|
||||
slartiFjords,
|
||||
hitchhikerHeartOfGold,
|
||||
hitchhikerPuzzle42
|
||||
];
|
||||
|
||||
const expected = [
|
||||
{
|
||||
name: "hitchhiker",
|
||||
repositories: [
|
||||
hitchhikerHeartOfGold,
|
||||
hitchhikerPuzzle42,
|
||||
hitchhikerRestand
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "slarti",
|
||||
repositories: [slartiFjords, slartiBlueprintsFjords]
|
||||
},
|
||||
{
|
||||
name: "zaphod",
|
||||
repositories: [zaphodMarvinFirmware]
|
||||
}
|
||||
];
|
||||
|
||||
expect(groupByNamespace(repositories)).toEqual(expected);
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import groupByNamespace from './groupByNamespace';
|
||||
|
||||
const base = {
|
||||
type: 'git',
|
||||
_links: {},
|
||||
};
|
||||
|
||||
const slartiBlueprintsFjords = {
|
||||
...base,
|
||||
namespace: 'slarti',
|
||||
name: 'fjords-blueprints',
|
||||
};
|
||||
|
||||
const slartiFjords = {
|
||||
...base,
|
||||
namespace: 'slarti',
|
||||
name: 'fjords',
|
||||
};
|
||||
|
||||
const hitchhikerRestand = {
|
||||
...base,
|
||||
namespace: 'hitchhiker',
|
||||
name: 'restand',
|
||||
};
|
||||
const hitchhikerPuzzle42 = {
|
||||
...base,
|
||||
namespace: 'hitchhiker',
|
||||
name: 'puzzle42',
|
||||
};
|
||||
|
||||
const hitchhikerHeartOfGold = {
|
||||
...base,
|
||||
namespace: 'hitchhiker',
|
||||
name: 'heartOfGold',
|
||||
};
|
||||
|
||||
const zaphodMarvinFirmware = {
|
||||
...base,
|
||||
namespace: 'zaphod',
|
||||
name: 'marvin-firmware',
|
||||
};
|
||||
|
||||
it('should group the repositories by their namespace', () => {
|
||||
const repositories = [
|
||||
zaphodMarvinFirmware,
|
||||
slartiBlueprintsFjords,
|
||||
hitchhikerRestand,
|
||||
slartiFjords,
|
||||
hitchhikerHeartOfGold,
|
||||
hitchhikerPuzzle42,
|
||||
];
|
||||
|
||||
const expected = [
|
||||
{
|
||||
name: 'hitchhiker',
|
||||
repositories: [
|
||||
hitchhikerHeartOfGold,
|
||||
hitchhikerPuzzle42,
|
||||
hitchhikerRestand,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'slarti',
|
||||
repositories: [slartiFjords, slartiBlueprintsFjords],
|
||||
},
|
||||
{
|
||||
name: 'zaphod',
|
||||
repositories: [zaphodMarvinFirmware],
|
||||
},
|
||||
];
|
||||
|
||||
expect(groupByNamespace(repositories)).toEqual(expected);
|
||||
});
|
||||
@@ -1,8 +1,7 @@
|
||||
// @flow
|
||||
import type { Repository, RepositoryGroup } from "@scm-manager/ui-types";
|
||||
import { Repository, RepositoryGroup } from '@scm-manager/ui-types';
|
||||
|
||||
export default function groupByNamespace(
|
||||
repositories: Repository[]
|
||||
repositories: Repository[],
|
||||
): RepositoryGroup[] {
|
||||
let groups = {};
|
||||
for (let repository of repositories) {
|
||||
@@ -12,7 +11,7 @@ export default function groupByNamespace(
|
||||
if (!group) {
|
||||
group = {
|
||||
name: groupName,
|
||||
repositories: []
|
||||
repositories: [],
|
||||
};
|
||||
groups[groupName] = group;
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
import RepositoryList from "./RepositoryList";
|
||||
export default RepositoryList;
|
||||
2
scm-ui/ui-webapp/src/repos/components/list/index.ts
Normal file
2
scm-ui/ui-webapp/src/repos/components/list/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import RepositoryList from './RepositoryList';
|
||||
export default RepositoryList;
|
||||
@@ -1,27 +1,26 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import type { Changeset, Repository } from "@scm-manager/ui-types";
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { Changeset, Repository } from '@scm-manager/ui-types';
|
||||
import {
|
||||
fetchChangesetIfNeeded,
|
||||
getChangeset,
|
||||
getFetchChangesetFailure,
|
||||
isFetchChangesetPending
|
||||
} from "../modules/changesets";
|
||||
import ChangesetDetails from "../components/changesets/ChangesetDetails";
|
||||
import { translate } from "react-i18next";
|
||||
import { ErrorPage, Loading } from "@scm-manager/ui-components";
|
||||
isFetchChangesetPending,
|
||||
} from '../modules/changesets';
|
||||
import ChangesetDetails from '../components/changesets/ChangesetDetails';
|
||||
import { translate } from 'react-i18next';
|
||||
import { ErrorPage, Loading } from '@scm-manager/ui-components';
|
||||
|
||||
type Props = {
|
||||
id: string,
|
||||
changeset: Changeset,
|
||||
repository: Repository,
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
fetchChangesetIfNeeded: (repository: Repository, id: string) => void,
|
||||
match: any,
|
||||
t: string => string
|
||||
id: string;
|
||||
changeset: Changeset;
|
||||
repository: Repository;
|
||||
loading: boolean;
|
||||
error: Error;
|
||||
fetchChangesetIfNeeded: (repository: Repository, id: string) => void;
|
||||
match: any;
|
||||
t: (p: string) => string;
|
||||
};
|
||||
|
||||
class ChangesetView extends React.Component<Props> {
|
||||
@@ -37,8 +36,8 @@ class ChangesetView extends React.Component<Props> {
|
||||
if (error) {
|
||||
return (
|
||||
<ErrorPage
|
||||
title={t("changesets.errorTitle")}
|
||||
subtitle={t("changesets.errorSubtitle")}
|
||||
title={t('changesets.errorTitle')}
|
||||
subtitle={t('changesets.errorSubtitle')}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
@@ -56,20 +55,24 @@ const mapStateToProps = (state, ownProps: Props) => {
|
||||
const changeset = getChangeset(state, repository, id);
|
||||
const loading = isFetchChangesetPending(state, repository, id);
|
||||
const error = getFetchChangesetFailure(state, repository, id);
|
||||
return { changeset, error, loading };
|
||||
return {
|
||||
changeset,
|
||||
error,
|
||||
loading,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
fetchChangesetIfNeeded: (repository: Repository, id: string) => {
|
||||
dispatch(fetchChangesetIfNeeded(repository, id));
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default withRouter(
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("repos")(ChangesetView))
|
||||
mapDispatchToProps,
|
||||
)(translate('repos')(ChangesetView)),
|
||||
);
|
||||
@@ -1,50 +1,48 @@
|
||||
// @flow
|
||||
|
||||
import React from "react";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import type {
|
||||
import React from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import {
|
||||
Branch,
|
||||
Changeset,
|
||||
PagedCollection,
|
||||
Repository
|
||||
} from "@scm-manager/ui-types";
|
||||
Repository,
|
||||
} from '@scm-manager/ui-types';
|
||||
import {
|
||||
fetchChangesets,
|
||||
getChangesets,
|
||||
getFetchChangesetsFailure,
|
||||
isFetchChangesetsPending,
|
||||
selectListAsCollection
|
||||
} from "../modules/changesets";
|
||||
selectListAsCollection,
|
||||
} from '../modules/changesets';
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
ErrorNotification,
|
||||
getPageFromMatch,
|
||||
LinkPaginator,
|
||||
ChangesetList,
|
||||
Loading,
|
||||
Notification
|
||||
} from "@scm-manager/ui-components";
|
||||
import { compose } from "redux";
|
||||
import { translate } from "react-i18next";
|
||||
Notification,
|
||||
} from '@scm-manager/ui-components';
|
||||
import { compose } from 'redux';
|
||||
import { translate } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
branch: Branch,
|
||||
page: number,
|
||||
repository: Repository;
|
||||
branch: Branch;
|
||||
page: number;
|
||||
|
||||
// State props
|
||||
changesets: Changeset[],
|
||||
list: PagedCollection,
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
changesets: Changeset[];
|
||||
list: PagedCollection;
|
||||
loading: boolean;
|
||||
error: Error;
|
||||
|
||||
// Dispatch props
|
||||
fetchChangesets: (Repository, Branch, number) => void,
|
||||
fetchChangesets: (p1: Repository, p2: Branch, p3: number) => void;
|
||||
|
||||
// context props
|
||||
match: any,
|
||||
t: string => string
|
||||
match: any;
|
||||
t: (p: string) => string;
|
||||
};
|
||||
|
||||
class Changesets extends React.Component<Props> {
|
||||
@@ -69,7 +67,7 @@ class Changesets extends React.Component<Props> {
|
||||
return (
|
||||
<div className="panel-block">
|
||||
<Notification type="info">
|
||||
{t("changesets.noChangesets")}
|
||||
{t('changesets.noChangesets')}
|
||||
</Notification>
|
||||
</div>
|
||||
);
|
||||
@@ -108,7 +106,7 @@ const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
fetchChangesets: (repo: Repository, branch: Branch, page: number) => {
|
||||
dispatch(fetchChangesets(repo, branch, page));
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -120,14 +118,20 @@ const mapStateToProps = (state: any, ownProps: Props) => {
|
||||
const list = selectListAsCollection(state, repository, branch);
|
||||
const page = getPageFromMatch(match);
|
||||
|
||||
return { changesets, list, page, loading, error };
|
||||
return {
|
||||
changesets,
|
||||
list,
|
||||
page,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
||||
export default compose(
|
||||
translate("repos"),
|
||||
translate('repos'),
|
||||
withRouter,
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)
|
||||
mapDispatchToProps,
|
||||
),
|
||||
)(Changesets);
|
||||
@@ -1,43 +1,41 @@
|
||||
// @flow
|
||||
|
||||
import React from "react";
|
||||
import type { Branch, Repository } from "@scm-manager/ui-types";
|
||||
import { translate } from "react-i18next";
|
||||
import { Route, withRouter } from "react-router-dom";
|
||||
import Changesets from "./Changesets";
|
||||
import { connect } from "react-redux";
|
||||
import React from 'react';
|
||||
import { Branch, Repository } from '@scm-manager/ui-types';
|
||||
import { translate } from 'react-i18next';
|
||||
import { Route, withRouter } from 'react-router-dom';
|
||||
import Changesets from './Changesets';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
BranchSelector,
|
||||
ErrorNotification,
|
||||
Loading
|
||||
} from "@scm-manager/ui-components";
|
||||
Loading,
|
||||
} from '@scm-manager/ui-components';
|
||||
import {
|
||||
fetchBranches,
|
||||
getBranches,
|
||||
getFetchBranchesFailure,
|
||||
isFetchBranchesPending
|
||||
} from "../branches/modules/branches";
|
||||
import { compose } from "redux";
|
||||
isFetchBranchesPending,
|
||||
} from '../branches/modules/branches';
|
||||
import { compose } from 'redux';
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
baseUrl: string,
|
||||
selected: string,
|
||||
baseUrlWithBranch: string,
|
||||
baseUrlWithoutBranch: string,
|
||||
repository: Repository;
|
||||
baseUrl: string;
|
||||
selected: string;
|
||||
baseUrlWithBranch: string;
|
||||
baseUrlWithoutBranch: string;
|
||||
|
||||
// State props
|
||||
branches: Branch[],
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
branches: Branch[];
|
||||
loading: boolean;
|
||||
error: Error;
|
||||
|
||||
// Dispatch props
|
||||
fetchBranches: Repository => void,
|
||||
fetchBranches: (p: Repository) => void;
|
||||
|
||||
// Context props
|
||||
history: any, // TODO flow type
|
||||
match: any,
|
||||
t: string => string
|
||||
history: any; // TODO flow type
|
||||
match: any;
|
||||
t: (p: string) => string;
|
||||
};
|
||||
|
||||
class ChangesetsRoot extends React.Component<Props> {
|
||||
@@ -49,7 +47,7 @@ class ChangesetsRoot extends React.Component<Props> {
|
||||
redirectToDefaultBranch = () => {
|
||||
if (this.shouldRedirectToDefaultBranch()) {
|
||||
const defaultBranches = this.props.branches.filter(
|
||||
b => b.defaultBranch === true
|
||||
b => b.defaultBranch === true,
|
||||
);
|
||||
if (defaultBranches.length > 0) {
|
||||
this.branchSelected(defaultBranches[0]);
|
||||
@@ -67,7 +65,7 @@ class ChangesetsRoot extends React.Component<Props> {
|
||||
};
|
||||
|
||||
stripEndingSlash = (url: string) => {
|
||||
if (url.endsWith("/")) {
|
||||
if (url.endsWith('/')) {
|
||||
return url.substring(0, url.length - 1);
|
||||
}
|
||||
return url;
|
||||
@@ -77,7 +75,7 @@ class ChangesetsRoot extends React.Component<Props> {
|
||||
let url;
|
||||
if (branch) {
|
||||
url = `${this.props.baseUrlWithBranch}/${encodeURIComponent(
|
||||
branch.name
|
||||
branch.name,
|
||||
)}/changesets/`;
|
||||
} else {
|
||||
url = `${this.props.baseUrlWithoutBranch}/`;
|
||||
@@ -123,7 +121,7 @@ class ChangesetsRoot extends React.Component<Props> {
|
||||
return (
|
||||
<div className="panel-heading">
|
||||
<BranchSelector
|
||||
label={t("changesets.branchSelectorLabel")}
|
||||
label={t('changesets.branchSelectorLabel')}
|
||||
branches={branches}
|
||||
selectedBranch={selected}
|
||||
selected={(b: Branch) => {
|
||||
@@ -141,7 +139,7 @@ const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
fetchBranches: (repo: Repository) => {
|
||||
dispatch(fetchBranches(repo));
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -156,15 +154,15 @@ const mapStateToProps = (state: any, ownProps: Props) => {
|
||||
loading,
|
||||
error,
|
||||
branches,
|
||||
selected
|
||||
selected,
|
||||
};
|
||||
};
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
translate("repos"),
|
||||
translate('repos'),
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)
|
||||
mapDispatchToProps,
|
||||
),
|
||||
)(ChangesetsRoot);
|
||||
@@ -1,50 +1,55 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import { Page } from "@scm-manager/ui-components";
|
||||
import RepositoryForm from "../components/form";
|
||||
import type { Repository, RepositoryType, NamespaceStrategies } from "@scm-manager/ui-types";
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { translate } from 'react-i18next';
|
||||
import { Page } from '@scm-manager/ui-components';
|
||||
import RepositoryForm from '../components/form';
|
||||
import {
|
||||
Repository,
|
||||
RepositoryType,
|
||||
NamespaceStrategies,
|
||||
} from '@scm-manager/ui-types';
|
||||
import {
|
||||
fetchRepositoryTypesIfNeeded,
|
||||
getFetchRepositoryTypesFailure,
|
||||
getRepositoryTypes,
|
||||
isFetchRepositoryTypesPending
|
||||
} from "../modules/repositoryTypes";
|
||||
isFetchRepositoryTypesPending,
|
||||
} from '../modules/repositoryTypes';
|
||||
import {
|
||||
createRepo,
|
||||
createRepoReset,
|
||||
getCreateRepoFailure,
|
||||
isCreateRepoPending
|
||||
} from "../modules/repos";
|
||||
import type { History } from "history";
|
||||
import { getRepositoriesLink } from "../../modules/indexResource";
|
||||
isCreateRepoPending,
|
||||
} from '../modules/repos';
|
||||
import { History } from 'history';
|
||||
import { getRepositoriesLink } from '../../modules/indexResource';
|
||||
import {
|
||||
fetchNamespaceStrategiesIfNeeded,
|
||||
getFetchNamespaceStrategiesFailure, getNamespaceStrategies, isFetchNamespaceStrategiesPending
|
||||
} from "../../admin/modules/namespaceStrategies";
|
||||
getFetchNamespaceStrategiesFailure,
|
||||
getNamespaceStrategies,
|
||||
isFetchNamespaceStrategiesPending,
|
||||
} from '../../admin/modules/namespaceStrategies';
|
||||
|
||||
type Props = {
|
||||
repositoryTypes: RepositoryType[],
|
||||
namespaceStrategies: NamespaceStrategies,
|
||||
pageLoading: boolean,
|
||||
createLoading: boolean,
|
||||
error: Error,
|
||||
repoLink: string,
|
||||
repositoryTypes: RepositoryType[];
|
||||
namespaceStrategies: NamespaceStrategies;
|
||||
pageLoading: boolean;
|
||||
createLoading: boolean;
|
||||
error: Error;
|
||||
repoLink: string;
|
||||
|
||||
// dispatch functions
|
||||
fetchNamespaceStrategiesIfNeeded: () => void,
|
||||
fetchRepositoryTypesIfNeeded: () => void,
|
||||
fetchNamespaceStrategiesIfNeeded: () => void;
|
||||
fetchRepositoryTypesIfNeeded: () => void;
|
||||
createRepo: (
|
||||
link: string,
|
||||
Repository,
|
||||
callback: (repo: Repository) => void
|
||||
) => void,
|
||||
resetForm: () => void,
|
||||
p2: Repository,
|
||||
callback: (repo: Repository) => void,
|
||||
) => void;
|
||||
resetForm: () => void;
|
||||
|
||||
// context props
|
||||
t: string => string,
|
||||
history: History
|
||||
t: (p: string) => string;
|
||||
history: History;
|
||||
};
|
||||
|
||||
class Create extends React.Component<Props> {
|
||||
@@ -57,7 +62,7 @@ class Create extends React.Component<Props> {
|
||||
repoCreated = (repo: Repository) => {
|
||||
const { history } = this.props;
|
||||
|
||||
history.push("/repo/" + repo.namespace + "/" + repo.name);
|
||||
history.push('/repo/' + repo.namespace + '/' + repo.name);
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -67,14 +72,14 @@ class Create extends React.Component<Props> {
|
||||
repositoryTypes,
|
||||
namespaceStrategies,
|
||||
createRepo,
|
||||
error
|
||||
error,
|
||||
} = this.props;
|
||||
|
||||
const { t, repoLink } = this.props;
|
||||
return (
|
||||
<Page
|
||||
title={t("create.title")}
|
||||
subtitle={t("create.subtitle")}
|
||||
title={t('create.title')}
|
||||
subtitle={t('create.subtitle')}
|
||||
loading={pageLoading}
|
||||
error={error}
|
||||
showContentOnError={true}
|
||||
@@ -85,7 +90,7 @@ class Create extends React.Component<Props> {
|
||||
namespaceStrategy={namespaceStrategies.current}
|
||||
submitForm={repo => {
|
||||
createRepo(repoLink, repo, (repo: Repository) =>
|
||||
this.repoCreated(repo)
|
||||
this.repoCreated(repo),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
@@ -97,12 +102,14 @@ class Create extends React.Component<Props> {
|
||||
const mapStateToProps = state => {
|
||||
const repositoryTypes = getRepositoryTypes(state);
|
||||
const namespaceStrategies = getNamespaceStrategies(state);
|
||||
const pageLoading = isFetchRepositoryTypesPending(state)
|
||||
|| isFetchNamespaceStrategiesPending(state);
|
||||
const pageLoading =
|
||||
isFetchRepositoryTypesPending(state) ||
|
||||
isFetchNamespaceStrategiesPending(state);
|
||||
const createLoading = isCreateRepoPending(state);
|
||||
const error = getFetchRepositoryTypesFailure(state)
|
||||
|| getCreateRepoFailure(state)
|
||||
|| getFetchNamespaceStrategiesFailure(state);
|
||||
const error =
|
||||
getFetchRepositoryTypesFailure(state) ||
|
||||
getCreateRepoFailure(state) ||
|
||||
getFetchNamespaceStrategiesFailure(state);
|
||||
const repoLink = getRepositoriesLink(state);
|
||||
return {
|
||||
repositoryTypes,
|
||||
@@ -110,7 +117,7 @@ const mapStateToProps = state => {
|
||||
pageLoading,
|
||||
createLoading,
|
||||
error,
|
||||
repoLink
|
||||
repoLink,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -125,17 +132,17 @@ const mapDispatchToProps = dispatch => {
|
||||
createRepo: (
|
||||
link: string,
|
||||
repository: Repository,
|
||||
callback: () => void
|
||||
callback: () => void,
|
||||
) => {
|
||||
dispatch(createRepo(link, repository, callback));
|
||||
},
|
||||
resetForm: () => {
|
||||
dispatch(createRepoReset());
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("repos")(Create));
|
||||
mapDispatchToProps,
|
||||
)(translate('repos')(Create));
|
||||
@@ -1,41 +1,40 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import React from 'react';
|
||||
import { translate } from 'react-i18next';
|
||||
import { Repository } from '@scm-manager/ui-types';
|
||||
import {
|
||||
Subtitle,
|
||||
DeleteButton,
|
||||
confirmAlert,
|
||||
ErrorNotification
|
||||
} from "@scm-manager/ui-components";
|
||||
ErrorNotification,
|
||||
} from '@scm-manager/ui-components';
|
||||
import {
|
||||
deleteRepo,
|
||||
getDeleteRepoFailure,
|
||||
isDeleteRepoPending
|
||||
} from "../modules/repos";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import type { History } from "history";
|
||||
isDeleteRepoPending,
|
||||
} from '../modules/repos';
|
||||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { History } from 'history';
|
||||
|
||||
type Props = {
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
repository: Repository,
|
||||
confirmDialog?: boolean,
|
||||
deleteRepo: (Repository, () => void) => void,
|
||||
loading: boolean;
|
||||
error: Error;
|
||||
repository: Repository;
|
||||
confirmDialog?: boolean;
|
||||
deleteRepo: (p1: Repository, p2: () => void) => void;
|
||||
|
||||
// context props
|
||||
history: History,
|
||||
t: string => string
|
||||
history: History;
|
||||
t: (p: string) => string;
|
||||
};
|
||||
|
||||
class DeleteRepo extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
confirmDialog: true
|
||||
confirmDialog: true,
|
||||
};
|
||||
|
||||
deleted = () => {
|
||||
this.props.history.push("/repos/");
|
||||
this.props.history.push('/repos/');
|
||||
};
|
||||
|
||||
deleteRepo = () => {
|
||||
@@ -45,18 +44,18 @@ class DeleteRepo extends React.Component<Props> {
|
||||
confirmDelete = () => {
|
||||
const { t } = this.props;
|
||||
confirmAlert({
|
||||
title: t("deleteRepo.confirmAlert.title"),
|
||||
message: t("deleteRepo.confirmAlert.message"),
|
||||
title: t('deleteRepo.confirmAlert.title'),
|
||||
message: t('deleteRepo.confirmAlert.message'),
|
||||
buttons: [
|
||||
{
|
||||
label: t("deleteRepo.confirmAlert.submit"),
|
||||
onClick: () => this.deleteRepo()
|
||||
label: t('deleteRepo.confirmAlert.submit'),
|
||||
onClick: () => this.deleteRepo(),
|
||||
},
|
||||
{
|
||||
label: t("deleteRepo.confirmAlert.cancel"),
|
||||
onClick: () => null
|
||||
}
|
||||
]
|
||||
label: t('deleteRepo.confirmAlert.cancel'),
|
||||
onClick: () => null,
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
@@ -75,12 +74,12 @@ class DeleteRepo extends React.Component<Props> {
|
||||
return (
|
||||
<>
|
||||
<hr />
|
||||
<Subtitle subtitle={t("deleteRepo.subtitle")} />
|
||||
<Subtitle subtitle={t('deleteRepo.subtitle')} />
|
||||
<ErrorNotification error={error} />
|
||||
<div className="columns">
|
||||
<div className="column">
|
||||
<DeleteButton
|
||||
label={t("deleteRepo.button")}
|
||||
label={t('deleteRepo.button')}
|
||||
action={action}
|
||||
loading={loading}
|
||||
/>
|
||||
@@ -97,7 +96,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
const error = getDeleteRepoFailure(state, namespace, name);
|
||||
return {
|
||||
loading,
|
||||
error
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -105,11 +104,11 @@ const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
deleteRepo: (repo: Repository, callback: () => void) => {
|
||||
dispatch(deleteRepo(repo, callback));
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(withRouter(translate("repos")(DeleteRepo)));
|
||||
mapDispatchToProps,
|
||||
)(withRouter(translate('repos')(DeleteRepo)));
|
||||
@@ -1,31 +1,30 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import RepositoryForm from "../components/form";
|
||||
import DeleteRepo from "./DeleteRepo";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import RepositoryForm from '../components/form';
|
||||
import DeleteRepo from './DeleteRepo';
|
||||
import { Repository } from '@scm-manager/ui-types';
|
||||
import {
|
||||
modifyRepo,
|
||||
isModifyRepoPending,
|
||||
getModifyRepoFailure,
|
||||
modifyRepoReset
|
||||
} from "../modules/repos";
|
||||
import type { History } from "history";
|
||||
import { ErrorNotification } from "@scm-manager/ui-components";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
modifyRepoReset,
|
||||
} from '../modules/repos';
|
||||
import { History } from 'history';
|
||||
import { ErrorNotification } from '@scm-manager/ui-components';
|
||||
import { ExtensionPoint } from '@scm-manager/ui-extensions';
|
||||
|
||||
type Props = {
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
loading: boolean;
|
||||
error: Error;
|
||||
|
||||
modifyRepo: (Repository, () => void) => void,
|
||||
modifyRepoReset: Repository => void,
|
||||
modifyRepo: (p1: Repository, p2: () => void) => void;
|
||||
modifyRepoReset: (p: Repository) => void;
|
||||
|
||||
// context props
|
||||
repository: Repository,
|
||||
history: History,
|
||||
match: any
|
||||
repository: Repository;
|
||||
history: History;
|
||||
match: any;
|
||||
};
|
||||
|
||||
class EditRepo extends React.Component<Props> {
|
||||
@@ -40,7 +39,7 @@ class EditRepo extends React.Component<Props> {
|
||||
};
|
||||
|
||||
stripEndingSlash = (url: string) => {
|
||||
if (url.endsWith("/")) {
|
||||
if (url.endsWith('/')) {
|
||||
return url.substring(0, url.length - 2);
|
||||
}
|
||||
return url;
|
||||
@@ -57,7 +56,7 @@ class EditRepo extends React.Component<Props> {
|
||||
|
||||
const extensionProps = {
|
||||
repository,
|
||||
url
|
||||
url,
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -87,7 +86,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
const error = getModifyRepoFailure(state, namespace, name);
|
||||
return {
|
||||
loading,
|
||||
error
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -98,11 +97,11 @@ const mapDispatchToProps = dispatch => {
|
||||
},
|
||||
modifyRepoReset: (repo: Repository) => {
|
||||
dispatch(modifyRepoReset(repo));
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
mapDispatchToProps,
|
||||
)(withRouter(EditRepo));
|
||||
@@ -1,17 +1,16 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import type { History } from "history";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import type { RepositoryCollection } from "@scm-manager/ui-types";
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { translate } from 'react-i18next';
|
||||
import { History } from 'history';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { RepositoryCollection } from '@scm-manager/ui-types';
|
||||
import {
|
||||
fetchReposByPage,
|
||||
getFetchReposFailure,
|
||||
getRepositoryCollection,
|
||||
isAbleToCreateRepos,
|
||||
isFetchReposPending
|
||||
} from "../modules/repos";
|
||||
isFetchReposPending,
|
||||
} from '../modules/repos';
|
||||
import {
|
||||
Page,
|
||||
PageActions,
|
||||
@@ -19,26 +18,26 @@ import {
|
||||
CreateButton,
|
||||
Notification,
|
||||
LinkPaginator,
|
||||
urls
|
||||
} from "@scm-manager/ui-components";
|
||||
import RepositoryList from "../components/list";
|
||||
import { getRepositoriesLink } from "../../modules/indexResource";
|
||||
urls,
|
||||
} from '@scm-manager/ui-components';
|
||||
import RepositoryList from '../components/list';
|
||||
import { getRepositoriesLink } from '../../modules/indexResource';
|
||||
|
||||
type Props = {
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
showCreateButton: boolean,
|
||||
collection: RepositoryCollection,
|
||||
page: number,
|
||||
reposLink: string,
|
||||
loading: boolean;
|
||||
error: Error;
|
||||
showCreateButton: boolean;
|
||||
collection: RepositoryCollection;
|
||||
page: number;
|
||||
reposLink: string;
|
||||
|
||||
// context props
|
||||
t: string => string,
|
||||
history: History,
|
||||
location: any,
|
||||
t: (p: string) => string;
|
||||
history: History;
|
||||
location: any;
|
||||
|
||||
// dispatched functions
|
||||
fetchReposByPage: (link: string, page: number, filter?: string) => void
|
||||
fetchReposByPage: (link: string, page: number, filter?: string) => void;
|
||||
};
|
||||
|
||||
class Overview extends React.Component<Props> {
|
||||
@@ -47,7 +46,7 @@ class Overview extends React.Component<Props> {
|
||||
fetchReposByPage(
|
||||
reposLink,
|
||||
page,
|
||||
urls.getQueryStringFromLocation(location)
|
||||
urls.getQueryStringFromLocation(location),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -58,7 +57,7 @@ class Overview extends React.Component<Props> {
|
||||
page,
|
||||
reposLink,
|
||||
location,
|
||||
fetchReposByPage
|
||||
fetchReposByPage,
|
||||
} = this.props;
|
||||
if (collection && page && !loading) {
|
||||
const statePage: number = collection.page + 1;
|
||||
@@ -66,7 +65,7 @@ class Overview extends React.Component<Props> {
|
||||
fetchReposByPage(
|
||||
reposLink,
|
||||
page,
|
||||
urls.getQueryStringFromLocation(location)
|
||||
urls.getQueryStringFromLocation(location),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -76,8 +75,8 @@ class Overview extends React.Component<Props> {
|
||||
const { error, loading, showCreateButton, t } = this.props;
|
||||
return (
|
||||
<Page
|
||||
title={t("overview.title")}
|
||||
subtitle={t("overview.subtitle")}
|
||||
title={t('overview.title')}
|
||||
subtitle={t('overview.subtitle')}
|
||||
loading={loading}
|
||||
error={error}
|
||||
>
|
||||
@@ -86,7 +85,7 @@ class Overview extends React.Component<Props> {
|
||||
<OverviewPageActions
|
||||
showCreateButton={showCreateButton}
|
||||
link="repos"
|
||||
label={t("overview.createButton")}
|
||||
label={t('overview.createButton')}
|
||||
/>
|
||||
</PageActions>
|
||||
</Page>
|
||||
@@ -109,7 +108,7 @@ class Overview extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Notification type="info">{t("overview.noRepositories")}</Notification>
|
||||
<Notification type="info">{t('overview.noRepositories')}</Notification>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -130,7 +129,7 @@ class Overview extends React.Component<Props> {
|
||||
const { showCreateButton, t } = this.props;
|
||||
if (showCreateButton) {
|
||||
return (
|
||||
<CreateButton label={t("overview.createButton")} link="/repos/create" />
|
||||
<CreateButton label={t('overview.createButton')} link="/repos/create" />
|
||||
);
|
||||
}
|
||||
return null;
|
||||
@@ -151,7 +150,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
error,
|
||||
page,
|
||||
showCreateButton,
|
||||
reposLink
|
||||
reposLink,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -159,10 +158,10 @@ const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
fetchReposByPage: (link: string, page: number, filter?: string) => {
|
||||
dispatch(fetchReposByPage(link, page, filter));
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("repos")(withRouter(Overview)));
|
||||
mapDispatchToProps,
|
||||
)(translate('repos')(withRouter(Overview)));
|
||||
@@ -1,15 +1,14 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
import {
|
||||
fetchRepoByName,
|
||||
getFetchRepoFailure,
|
||||
getRepository,
|
||||
isFetchRepoPending
|
||||
} from "../modules/repos";
|
||||
isFetchRepoPending,
|
||||
} from '../modules/repos';
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { Redirect, Route, Switch } from "react-router-dom";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import { connect } from 'react-redux';
|
||||
import { Redirect, Route, Switch } from 'react-router-dom';
|
||||
import { Repository } from '@scm-manager/ui-types';
|
||||
|
||||
import {
|
||||
ErrorPage,
|
||||
@@ -18,42 +17,42 @@ import {
|
||||
NavLink,
|
||||
Page,
|
||||
Section,
|
||||
SubNavigation
|
||||
} from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
import RepositoryDetails from "../components/RepositoryDetails";
|
||||
import EditRepo from "./EditRepo";
|
||||
import BranchesOverview from "../branches/containers/BranchesOverview";
|
||||
import CreateBranch from "../branches/containers/CreateBranch";
|
||||
import Permissions from "../permissions/containers/Permissions";
|
||||
SubNavigation,
|
||||
} from '@scm-manager/ui-components';
|
||||
import { translate } from 'react-i18next';
|
||||
import RepositoryDetails from '../components/RepositoryDetails';
|
||||
import EditRepo from './EditRepo';
|
||||
import BranchesOverview from '../branches/containers/BranchesOverview';
|
||||
import CreateBranch from '../branches/containers/CreateBranch';
|
||||
import Permissions from '../permissions/containers/Permissions';
|
||||
|
||||
import type { History } from "history";
|
||||
import EditRepoNavLink from "../components/EditRepoNavLink";
|
||||
import BranchRoot from "../branches/containers/BranchRoot";
|
||||
import ChangesetsRoot from "./ChangesetsRoot";
|
||||
import ChangesetView from "./ChangesetView";
|
||||
import PermissionsNavLink from "../components/PermissionsNavLink";
|
||||
import Sources from "../sources/containers/Sources";
|
||||
import RepositoryNavLink from "../components/RepositoryNavLink";
|
||||
import { getLinks, getRepositoriesLink } from "../../modules/indexResource";
|
||||
import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import { History } from 'history';
|
||||
import EditRepoNavLink from '../components/EditRepoNavLink';
|
||||
import BranchRoot from '../branches/containers/BranchRoot';
|
||||
import ChangesetsRoot from './ChangesetsRoot';
|
||||
import ChangesetView from './ChangesetView';
|
||||
import PermissionsNavLink from '../components/PermissionsNavLink';
|
||||
import Sources from '../sources/containers/Sources';
|
||||
import RepositoryNavLink from '../components/RepositoryNavLink';
|
||||
import { getLinks, getRepositoriesLink } from '../../modules/indexResource';
|
||||
import { binder, ExtensionPoint } from '@scm-manager/ui-extensions';
|
||||
|
||||
type Props = {
|
||||
namespace: string,
|
||||
name: string,
|
||||
repository: Repository,
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
repoLink: string,
|
||||
indexLinks: Object,
|
||||
namespace: string;
|
||||
name: string;
|
||||
repository: Repository;
|
||||
loading: boolean;
|
||||
error: Error;
|
||||
repoLink: string;
|
||||
indexLinks: object;
|
||||
|
||||
// dispatch functions
|
||||
fetchRepoByName: (link: string, namespace: string, name: string) => void,
|
||||
fetchRepoByName: (link: string, namespace: string, name: string) => void;
|
||||
|
||||
// context props
|
||||
t: string => string,
|
||||
history: History,
|
||||
match: any
|
||||
t: (p: string) => string;
|
||||
history: History;
|
||||
match: any;
|
||||
};
|
||||
|
||||
class RepositoryRoot extends React.Component<Props> {
|
||||
@@ -64,7 +63,7 @@ class RepositoryRoot extends React.Component<Props> {
|
||||
}
|
||||
|
||||
stripEndingSlash = (url: string) => {
|
||||
if (url.endsWith("/")) {
|
||||
if (url.endsWith('/')) {
|
||||
return url.substring(0, url.length - 1);
|
||||
}
|
||||
return url;
|
||||
@@ -92,8 +91,8 @@ class RepositoryRoot extends React.Component<Props> {
|
||||
if (error) {
|
||||
return (
|
||||
<ErrorPage
|
||||
title={t("repositoryRoot.errorTitle")}
|
||||
subtitle={t("repositoryRoot.errorSubtitle")}
|
||||
title={t('repositoryRoot.errorTitle')}
|
||||
subtitle={t('repositoryRoot.errorSubtitle')}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
@@ -108,22 +107,22 @@ class RepositoryRoot extends React.Component<Props> {
|
||||
const extensionProps = {
|
||||
repository,
|
||||
url,
|
||||
indexLinks
|
||||
indexLinks,
|
||||
};
|
||||
|
||||
const redirectUrlFactory = binder.getExtension(
|
||||
"repository.redirect",
|
||||
this.props
|
||||
'repository.redirect',
|
||||
this.props,
|
||||
);
|
||||
let redirectedUrl;
|
||||
if (redirectUrlFactory) {
|
||||
redirectedUrl = url + redirectUrlFactory(this.props);
|
||||
} else {
|
||||
redirectedUrl = url + "/info";
|
||||
redirectedUrl = url + '/info';
|
||||
}
|
||||
|
||||
return (
|
||||
<Page title={repository.namespace + "/" + repository.name}>
|
||||
<Page title={repository.namespace + '/' + repository.name}>
|
||||
<div className="columns">
|
||||
<div className="column is-three-quarters">
|
||||
<Switch>
|
||||
@@ -216,7 +215,7 @@ class RepositoryRoot extends React.Component<Props> {
|
||||
</div>
|
||||
<div className="column">
|
||||
<Navigation>
|
||||
<Section label={t("repositoryRoot.menu.navigationLabel")}>
|
||||
<Section label={t('repositoryRoot.menu.navigationLabel')}>
|
||||
<ExtensionPoint
|
||||
name="repository.navigation.topLevel"
|
||||
props={extensionProps}
|
||||
@@ -225,14 +224,14 @@ class RepositoryRoot extends React.Component<Props> {
|
||||
<NavLink
|
||||
to={`${url}/info`}
|
||||
icon="fas fa-info-circle"
|
||||
label={t("repositoryRoot.menu.informationNavLink")}
|
||||
label={t('repositoryRoot.menu.informationNavLink')}
|
||||
/>
|
||||
<RepositoryNavLink
|
||||
repository={repository}
|
||||
linkName="branches"
|
||||
to={`${url}/branches/`}
|
||||
icon="fas fa-code-branch"
|
||||
label={t("repositoryRoot.menu.branchesNavLink")}
|
||||
label={t('repositoryRoot.menu.branchesNavLink')}
|
||||
activeWhenMatch={this.matchesBranches}
|
||||
activeOnlyWhenExact={false}
|
||||
/>
|
||||
@@ -241,7 +240,7 @@ class RepositoryRoot extends React.Component<Props> {
|
||||
linkName="changesets"
|
||||
to={`${url}/changesets/`}
|
||||
icon="fas fa-exchange-alt"
|
||||
label={t("repositoryRoot.menu.historyNavLink")}
|
||||
label={t('repositoryRoot.menu.historyNavLink')}
|
||||
activeWhenMatch={this.matchesChangesets}
|
||||
activeOnlyWhenExact={false}
|
||||
/>
|
||||
@@ -250,7 +249,7 @@ class RepositoryRoot extends React.Component<Props> {
|
||||
linkName="sources"
|
||||
to={`${url}/sources`}
|
||||
icon="fas fa-code"
|
||||
label={t("repositoryRoot.menu.sourcesNavLink")}
|
||||
label={t('repositoryRoot.menu.sourcesNavLink')}
|
||||
activeOnlyWhenExact={false}
|
||||
/>
|
||||
<ExtensionPoint
|
||||
@@ -260,7 +259,7 @@ class RepositoryRoot extends React.Component<Props> {
|
||||
/>
|
||||
<SubNavigation
|
||||
to={`${url}/settings/general`}
|
||||
label={t("repositoryRoot.menu.settingsNavLink")}
|
||||
label={t('repositoryRoot.menu.settingsNavLink')}
|
||||
>
|
||||
<EditRepoNavLink
|
||||
repository={repository}
|
||||
@@ -299,7 +298,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
loading,
|
||||
error,
|
||||
repoLink,
|
||||
indexLinks
|
||||
indexLinks,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -307,11 +306,11 @@ const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
fetchRepoByName: (link: string, namespace: string, name: string) => {
|
||||
dispatch(fetchRepoByName(link, namespace, name));
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("repos")(RepositoryRoot));
|
||||
mapDispatchToProps,
|
||||
)(translate('repos')(RepositoryRoot));
|
||||
@@ -1,620 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import configureMockStore from "redux-mock-store";
|
||||
import thunk from "redux-thunk";
|
||||
import fetchMock from "fetch-mock";
|
||||
import reducer, {
|
||||
FETCH_CHANGESET,
|
||||
FETCH_CHANGESET_FAILURE,
|
||||
FETCH_CHANGESET_PENDING,
|
||||
FETCH_CHANGESET_SUCCESS,
|
||||
FETCH_CHANGESETS,
|
||||
FETCH_CHANGESETS_FAILURE,
|
||||
FETCH_CHANGESETS_PENDING,
|
||||
FETCH_CHANGESETS_SUCCESS,
|
||||
fetchChangeset,
|
||||
fetchChangesetIfNeeded,
|
||||
fetchChangesets,
|
||||
fetchChangesetsSuccess,
|
||||
fetchChangesetSuccess,
|
||||
getChangeset,
|
||||
getChangesets,
|
||||
getFetchChangesetFailure,
|
||||
getFetchChangesetsFailure,
|
||||
isFetchChangesetPending,
|
||||
isFetchChangesetsPending,
|
||||
selectListAsCollection,
|
||||
shouldFetchChangeset
|
||||
} from "./changesets";
|
||||
|
||||
const branch = {
|
||||
name: "specific",
|
||||
revision: "123",
|
||||
_links: {
|
||||
history: {
|
||||
href:
|
||||
"http://scm.hitchhicker.com/api/v2/repositories/foo/bar/branches/specific/changesets"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const repository = {
|
||||
namespace: "foo",
|
||||
name: "bar",
|
||||
type: "GIT",
|
||||
_links: {
|
||||
self: {
|
||||
href: "http://scm.hitchhicker.com/api/v2/repositories/foo/bar"
|
||||
},
|
||||
changesets: {
|
||||
href: "http://scm.hitchhicker.com/api/v2/repositories/foo/bar/changesets"
|
||||
},
|
||||
branches: {
|
||||
href:
|
||||
"http://scm.hitchhicker.com/api/v2/repositories/foo/bar/branches/specific/branches"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const changesets = {};
|
||||
|
||||
describe("changesets", () => {
|
||||
describe("fetching of changesets", () => {
|
||||
const DEFAULT_BRANCH_URL =
|
||||
"http://scm.hitchhicker.com/api/v2/repositories/foo/bar/changesets";
|
||||
const SPECIFIC_BRANCH_URL =
|
||||
"http://scm.hitchhicker.com/api/v2/repositories/foo/bar/branches/specific/changesets";
|
||||
|
||||
const mockStore = configureMockStore([thunk]);
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
const changesetId = "aba876c0625d90a6aff1494f3d161aaa7008b958";
|
||||
|
||||
it("should fetch changeset", () => {
|
||||
fetchMock.getOnce(DEFAULT_BRANCH_URL + "/" + changesetId, "{}");
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_CHANGESET_PENDING,
|
||||
itemId: "foo/bar/" + changesetId
|
||||
},
|
||||
{
|
||||
type: FETCH_CHANGESET_SUCCESS,
|
||||
payload: {
|
||||
changeset: {},
|
||||
id: changesetId,
|
||||
repository: repository
|
||||
},
|
||||
itemId: "foo/bar/" + changesetId
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(fetchChangeset(repository, changesetId))
|
||||
.then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail fetching changeset on error", () => {
|
||||
fetchMock.getOnce(DEFAULT_BRANCH_URL + "/" + changesetId, 500);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_CHANGESET_PENDING,
|
||||
itemId: "foo/bar/" + changesetId
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(fetchChangeset(repository, changesetId))
|
||||
.then(() => {
|
||||
expect(store.getActions()[0]).toEqual(expectedActions[0]);
|
||||
expect(store.getActions()[1].type).toEqual(FETCH_CHANGESET_FAILURE);
|
||||
expect(store.getActions()[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("should fetch changeset if needed", () => {
|
||||
fetchMock.getOnce(DEFAULT_BRANCH_URL + "/id3", "{}");
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_CHANGESET_PENDING,
|
||||
itemId: "foo/bar/id3"
|
||||
},
|
||||
{
|
||||
type: FETCH_CHANGESET_SUCCESS,
|
||||
payload: {
|
||||
changeset: {},
|
||||
id: "id3",
|
||||
repository: repository
|
||||
},
|
||||
itemId: "foo/bar/id3"
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(fetchChangesetIfNeeded(repository, "id3"))
|
||||
.then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should not fetch changeset if not needed", () => {
|
||||
fetchMock.getOnce(DEFAULT_BRANCH_URL + "/id1", 500);
|
||||
|
||||
const state = {
|
||||
changesets: {
|
||||
"foo/bar": {
|
||||
byId: {
|
||||
id1: { id: "id1" },
|
||||
id2: { id: "id2" }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const store = mockStore(state);
|
||||
return expect(
|
||||
store.dispatch(fetchChangesetIfNeeded(repository, "id1"))
|
||||
).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("should fetch changesets for default branch", () => {
|
||||
fetchMock.getOnce(DEFAULT_BRANCH_URL, "{}");
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_CHANGESETS_PENDING,
|
||||
itemId: "foo/bar"
|
||||
},
|
||||
{
|
||||
type: FETCH_CHANGESETS_SUCCESS,
|
||||
payload: {
|
||||
repository,
|
||||
undefined,
|
||||
changesets
|
||||
},
|
||||
itemId: "foo/bar"
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchChangesets(repository)).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should fetch changesets for specific branch", () => {
|
||||
const itemId = "foo/bar/specific";
|
||||
fetchMock.getOnce(SPECIFIC_BRANCH_URL, "{}");
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_CHANGESETS_PENDING,
|
||||
itemId
|
||||
},
|
||||
{
|
||||
type: FETCH_CHANGESETS_SUCCESS,
|
||||
payload: {
|
||||
repository,
|
||||
branch,
|
||||
changesets
|
||||
},
|
||||
itemId
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchChangesets(repository, branch)).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail fetching changesets on error", () => {
|
||||
const itemId = "foo/bar";
|
||||
fetchMock.getOnce(DEFAULT_BRANCH_URL, 500);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_CHANGESETS_PENDING,
|
||||
itemId
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchChangesets(repository)).then(() => {
|
||||
expect(store.getActions()[0]).toEqual(expectedActions[0]);
|
||||
expect(store.getActions()[1].type).toEqual(FETCH_CHANGESETS_FAILURE);
|
||||
expect(store.getActions()[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail fetching changesets for specific branch on error", () => {
|
||||
const itemId = "foo/bar/specific";
|
||||
fetchMock.getOnce(SPECIFIC_BRANCH_URL, 500);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_CHANGESETS_PENDING,
|
||||
itemId
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchChangesets(repository, branch)).then(() => {
|
||||
expect(store.getActions()[0]).toEqual(expectedActions[0]);
|
||||
expect(store.getActions()[1].type).toEqual(FETCH_CHANGESETS_FAILURE);
|
||||
expect(store.getActions()[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("should fetch changesets by page", () => {
|
||||
fetchMock.getOnce(DEFAULT_BRANCH_URL + "?page=4", "{}");
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_CHANGESETS_PENDING,
|
||||
itemId: "foo/bar"
|
||||
},
|
||||
{
|
||||
type: FETCH_CHANGESETS_SUCCESS,
|
||||
payload: {
|
||||
repository,
|
||||
undefined,
|
||||
changesets
|
||||
},
|
||||
itemId: "foo/bar"
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(fetchChangesets(repository, undefined, 5))
|
||||
.then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should fetch changesets by branch and page", () => {
|
||||
fetchMock.getOnce(SPECIFIC_BRANCH_URL + "?page=4", "{}");
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_CHANGESETS_PENDING,
|
||||
itemId: "foo/bar/specific"
|
||||
},
|
||||
{
|
||||
type: FETCH_CHANGESETS_SUCCESS,
|
||||
payload: {
|
||||
repository,
|
||||
branch,
|
||||
changesets
|
||||
},
|
||||
itemId: "foo/bar/specific"
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchChangesets(repository, branch, 5)).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("changesets reducer", () => {
|
||||
const responseBody = {
|
||||
page: 1,
|
||||
pageTotal: 10,
|
||||
_links: {},
|
||||
_embedded: {
|
||||
changesets: [
|
||||
{ id: "changeset1", author: { mail: "z@phod.com", name: "zaphod" } },
|
||||
{ id: "changeset2", description: "foo" },
|
||||
{ id: "changeset3", description: "bar" }
|
||||
],
|
||||
_embedded: {
|
||||
tags: [],
|
||||
branches: [],
|
||||
parents: []
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
it("should set state to received changesets", () => {
|
||||
const newState = reducer(
|
||||
{},
|
||||
fetchChangesetsSuccess(repository, undefined, responseBody)
|
||||
);
|
||||
expect(newState).toBeDefined();
|
||||
expect(newState["foo/bar"].byId["changeset1"].author.mail).toEqual(
|
||||
"z@phod.com"
|
||||
);
|
||||
expect(newState["foo/bar"].byId["changeset2"].description).toEqual("foo");
|
||||
expect(newState["foo/bar"].byId["changeset3"].description).toEqual("bar");
|
||||
expect(newState["foo/bar"].byBranch[""]).toEqual({
|
||||
entry: {
|
||||
page: 1,
|
||||
pageTotal: 10,
|
||||
_links: {}
|
||||
},
|
||||
entries: ["changeset1", "changeset2", "changeset3"]
|
||||
});
|
||||
});
|
||||
|
||||
it("should store the changeset list to branch", () => {
|
||||
const newState = reducer(
|
||||
{},
|
||||
fetchChangesetsSuccess(repository, branch, responseBody)
|
||||
);
|
||||
|
||||
expect(newState["foo/bar"].byId["changeset1"]).toBeDefined();
|
||||
expect(newState["foo/bar"].byBranch["specific"].entries).toEqual([
|
||||
"changeset1",
|
||||
"changeset2",
|
||||
"changeset3"
|
||||
]);
|
||||
});
|
||||
|
||||
it("should not remove existing changesets", () => {
|
||||
const state = {
|
||||
"foo/bar": {
|
||||
byId: {
|
||||
id2: { id: "id2" },
|
||||
id1: { id: "id1" }
|
||||
},
|
||||
byBranch: {
|
||||
"": {
|
||||
entries: ["id1", "id2"]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const newState = reducer(
|
||||
state,
|
||||
fetchChangesetsSuccess(repository, undefined, responseBody)
|
||||
);
|
||||
|
||||
const fooBar = newState["foo/bar"];
|
||||
|
||||
expect(fooBar.byBranch[""].entries).toEqual([
|
||||
"changeset1",
|
||||
"changeset2",
|
||||
"changeset3"
|
||||
]);
|
||||
expect(fooBar.byId["id2"]).toEqual({ id: "id2" });
|
||||
expect(fooBar.byId["id1"]).toEqual({ id: "id1" });
|
||||
});
|
||||
|
||||
const responseBodySingleChangeset = {
|
||||
id: "id3",
|
||||
author: {
|
||||
mail: "z@phod.com",
|
||||
name: "zaphod"
|
||||
},
|
||||
date: "2018-09-13T08:46:22Z",
|
||||
description: "added testChangeset",
|
||||
_links: {},
|
||||
_embedded: {
|
||||
tags: [],
|
||||
branches: []
|
||||
}
|
||||
};
|
||||
|
||||
it("should add changeset to state", () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
"foo/bar": {
|
||||
byId: {
|
||||
"id2": {
|
||||
id: "id2",
|
||||
author: { mail: "mail@author.com", name: "author" }
|
||||
}
|
||||
},
|
||||
list: {
|
||||
entry: {
|
||||
page: 1,
|
||||
pageTotal: 10,
|
||||
_links: {}
|
||||
},
|
||||
entries: ["id2"]
|
||||
}
|
||||
}
|
||||
},
|
||||
fetchChangesetSuccess(responseBodySingleChangeset, repository, "id3")
|
||||
);
|
||||
|
||||
expect(newState).toBeDefined();
|
||||
expect(newState["foo/bar"].byId["id3"].description).toEqual(
|
||||
"added testChangeset"
|
||||
);
|
||||
expect(newState["foo/bar"].byId["id3"].author.mail).toEqual("z@phod.com");
|
||||
expect(newState["foo/bar"].byId["id2"]).toBeDefined();
|
||||
expect(newState["foo/bar"].byId["id3"]).toBeDefined();
|
||||
expect(newState["foo/bar"].list).toEqual({
|
||||
entry: {
|
||||
page: 1,
|
||||
pageTotal: 10,
|
||||
_links: {}
|
||||
},
|
||||
entries: ["id2"]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("changeset selectors", () => {
|
||||
const error = new Error("Something went wrong");
|
||||
|
||||
it("should return changeset", () => {
|
||||
const state = {
|
||||
changesets: {
|
||||
"foo/bar": {
|
||||
byId: {
|
||||
id1: { id: "id1" },
|
||||
id2: { id: "id2" }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const result = getChangeset(state, repository, "id1");
|
||||
expect(result).toEqual({ id: "id1" });
|
||||
});
|
||||
|
||||
it("should return null if changeset does not exist", () => {
|
||||
const state = {
|
||||
changesets: {
|
||||
"foo/bar": {
|
||||
byId: {
|
||||
id1: { id: "id1" },
|
||||
id2: { id: "id2" }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const result = getChangeset(state, repository, "id3");
|
||||
expect(result).toEqual(null);
|
||||
});
|
||||
|
||||
it("should return true if changeset does not exist", () => {
|
||||
const state = {
|
||||
changesets: {
|
||||
"foo/bar": {
|
||||
byId: {
|
||||
id1: { id: "id1" },
|
||||
id2: { id: "id2" }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const result = shouldFetchChangeset(state, repository, "id3");
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it("should return false if changeset exists", () => {
|
||||
const state = {
|
||||
changesets: {
|
||||
"foo/bar": {
|
||||
byId: {
|
||||
id1: { id: "id1" },
|
||||
id2: { id: "id2" }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const result = shouldFetchChangeset(state, repository, "id2");
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return true, when fetching changeset is pending", () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[FETCH_CHANGESET + "/foo/bar/id1"]: true
|
||||
}
|
||||
};
|
||||
|
||||
expect(isFetchChangesetPending(state, repository, "id1")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should return false, when fetching changeset is not pending", () => {
|
||||
expect(isFetchChangesetPending({}, repository, "id1")).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return error if fetching changeset failed", () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[FETCH_CHANGESET + "/foo/bar/id1"]: error
|
||||
}
|
||||
};
|
||||
|
||||
expect(getFetchChangesetFailure(state, repository, "id1")).toEqual(error);
|
||||
});
|
||||
|
||||
it("should return false if fetching changeset did not fail", () => {
|
||||
expect(getFetchChangesetFailure({}, repository, "id1")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should get all changesets for a given repository", () => {
|
||||
const state = {
|
||||
changesets: {
|
||||
"foo/bar": {
|
||||
byId: {
|
||||
id2: { id: "id2" },
|
||||
id1: { id: "id1" }
|
||||
},
|
||||
byBranch: {
|
||||
"": {
|
||||
entries: ["id1", "id2"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const result = getChangesets(state, repository);
|
||||
expect(result).toEqual([{ id: "id1" }, { id: "id2" }]);
|
||||
});
|
||||
|
||||
it("should return true, when fetching changesets is pending", () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[FETCH_CHANGESETS + "/foo/bar"]: true
|
||||
}
|
||||
};
|
||||
|
||||
expect(isFetchChangesetsPending(state, repository)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should return false, when fetching changesets is not pending", () => {
|
||||
expect(isFetchChangesetsPending({}, repository)).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return error if fetching changesets failed", () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[FETCH_CHANGESETS + "/foo/bar"]: error
|
||||
}
|
||||
};
|
||||
|
||||
expect(getFetchChangesetsFailure(state, repository)).toEqual(error);
|
||||
});
|
||||
|
||||
it("should return false if fetching changesets did not fail", () => {
|
||||
expect(getFetchChangesetsFailure({}, repository)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return list as collection for the default branch", () => {
|
||||
const state = {
|
||||
changesets: {
|
||||
"foo/bar": {
|
||||
byId: {
|
||||
id2: { id: "id2" },
|
||||
id1: { id: "id1" }
|
||||
},
|
||||
byBranch: {
|
||||
"": {
|
||||
entry: {
|
||||
page: 1,
|
||||
pageTotal: 10,
|
||||
_links: {}
|
||||
},
|
||||
entries: ["id1", "id2"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const collection = selectListAsCollection(state, repository);
|
||||
expect(collection.page).toBe(1);
|
||||
expect(collection.pageTotal).toBe(10);
|
||||
});
|
||||
});
|
||||
});
|
||||
678
scm-ui/ui-webapp/src/repos/modules/changesets.test.ts
Normal file
678
scm-ui/ui-webapp/src/repos/modules/changesets.test.ts
Normal file
@@ -0,0 +1,678 @@
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import reducer, {
|
||||
FETCH_CHANGESET,
|
||||
FETCH_CHANGESET_FAILURE,
|
||||
FETCH_CHANGESET_PENDING,
|
||||
FETCH_CHANGESET_SUCCESS,
|
||||
FETCH_CHANGESETS,
|
||||
FETCH_CHANGESETS_FAILURE,
|
||||
FETCH_CHANGESETS_PENDING,
|
||||
FETCH_CHANGESETS_SUCCESS,
|
||||
fetchChangeset,
|
||||
fetchChangesetIfNeeded,
|
||||
fetchChangesets,
|
||||
fetchChangesetsSuccess,
|
||||
fetchChangesetSuccess,
|
||||
getChangeset,
|
||||
getChangesets,
|
||||
getFetchChangesetFailure,
|
||||
getFetchChangesetsFailure,
|
||||
isFetchChangesetPending,
|
||||
isFetchChangesetsPending,
|
||||
selectListAsCollection,
|
||||
shouldFetchChangeset,
|
||||
} from './changesets';
|
||||
|
||||
const branch = {
|
||||
name: 'specific',
|
||||
revision: '123',
|
||||
_links: {
|
||||
history: {
|
||||
href:
|
||||
'http://scm.hitchhicker.com/api/v2/repositories/foo/bar/branches/specific/changesets',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const repository = {
|
||||
namespace: 'foo',
|
||||
name: 'bar',
|
||||
type: 'GIT',
|
||||
_links: {
|
||||
self: {
|
||||
href: 'http://scm.hitchhicker.com/api/v2/repositories/foo/bar',
|
||||
},
|
||||
changesets: {
|
||||
href: 'http://scm.hitchhicker.com/api/v2/repositories/foo/bar/changesets',
|
||||
},
|
||||
branches: {
|
||||
href:
|
||||
'http://scm.hitchhicker.com/api/v2/repositories/foo/bar/branches/specific/branches',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const changesets = {};
|
||||
|
||||
describe('changesets', () => {
|
||||
describe('fetching of changesets', () => {
|
||||
const DEFAULT_BRANCH_URL =
|
||||
'http://scm.hitchhicker.com/api/v2/repositories/foo/bar/changesets';
|
||||
const SPECIFIC_BRANCH_URL =
|
||||
'http://scm.hitchhicker.com/api/v2/repositories/foo/bar/branches/specific/changesets';
|
||||
|
||||
const mockStore = configureMockStore([thunk]);
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
const changesetId = 'aba876c0625d90a6aff1494f3d161aaa7008b958';
|
||||
|
||||
it('should fetch changeset', () => {
|
||||
fetchMock.getOnce(DEFAULT_BRANCH_URL + '/' + changesetId, '{}');
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_CHANGESET_PENDING,
|
||||
itemId: 'foo/bar/' + changesetId,
|
||||
},
|
||||
{
|
||||
type: FETCH_CHANGESET_SUCCESS,
|
||||
payload: {
|
||||
changeset: {},
|
||||
id: changesetId,
|
||||
repository: repository,
|
||||
},
|
||||
itemId: 'foo/bar/' + changesetId,
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(fetchChangeset(repository, changesetId))
|
||||
.then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail fetching changeset on error', () => {
|
||||
fetchMock.getOnce(DEFAULT_BRANCH_URL + '/' + changesetId, 500);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_CHANGESET_PENDING,
|
||||
itemId: 'foo/bar/' + changesetId,
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(fetchChangeset(repository, changesetId))
|
||||
.then(() => {
|
||||
expect(store.getActions()[0]).toEqual(expectedActions[0]);
|
||||
expect(store.getActions()[1].type).toEqual(FETCH_CHANGESET_FAILURE);
|
||||
expect(store.getActions()[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fetch changeset if needed', () => {
|
||||
fetchMock.getOnce(DEFAULT_BRANCH_URL + '/id3', '{}');
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_CHANGESET_PENDING,
|
||||
itemId: 'foo/bar/id3',
|
||||
},
|
||||
{
|
||||
type: FETCH_CHANGESET_SUCCESS,
|
||||
payload: {
|
||||
changeset: {},
|
||||
id: 'id3',
|
||||
repository: repository,
|
||||
},
|
||||
itemId: 'foo/bar/id3',
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(fetchChangesetIfNeeded(repository, 'id3'))
|
||||
.then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not fetch changeset if not needed', () => {
|
||||
fetchMock.getOnce(DEFAULT_BRANCH_URL + '/id1', 500);
|
||||
|
||||
const state = {
|
||||
changesets: {
|
||||
'foo/bar': {
|
||||
byId: {
|
||||
id1: {
|
||||
id: 'id1',
|
||||
},
|
||||
id2: {
|
||||
id: 'id2',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const store = mockStore(state);
|
||||
return expect(
|
||||
store.dispatch(fetchChangesetIfNeeded(repository, 'id1')),
|
||||
).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('should fetch changesets for default branch', () => {
|
||||
fetchMock.getOnce(DEFAULT_BRANCH_URL, '{}');
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_CHANGESETS_PENDING,
|
||||
itemId: 'foo/bar',
|
||||
},
|
||||
{
|
||||
type: FETCH_CHANGESETS_SUCCESS,
|
||||
payload: {
|
||||
repository,
|
||||
undefined,
|
||||
changesets,
|
||||
},
|
||||
itemId: 'foo/bar',
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchChangesets(repository)).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fetch changesets for specific branch', () => {
|
||||
const itemId = 'foo/bar/specific';
|
||||
fetchMock.getOnce(SPECIFIC_BRANCH_URL, '{}');
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_CHANGESETS_PENDING,
|
||||
itemId,
|
||||
},
|
||||
{
|
||||
type: FETCH_CHANGESETS_SUCCESS,
|
||||
payload: {
|
||||
repository,
|
||||
branch,
|
||||
changesets,
|
||||
},
|
||||
itemId,
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchChangesets(repository, branch)).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail fetching changesets on error', () => {
|
||||
const itemId = 'foo/bar';
|
||||
fetchMock.getOnce(DEFAULT_BRANCH_URL, 500);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_CHANGESETS_PENDING,
|
||||
itemId,
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchChangesets(repository)).then(() => {
|
||||
expect(store.getActions()[0]).toEqual(expectedActions[0]);
|
||||
expect(store.getActions()[1].type).toEqual(FETCH_CHANGESETS_FAILURE);
|
||||
expect(store.getActions()[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail fetching changesets for specific branch on error', () => {
|
||||
const itemId = 'foo/bar/specific';
|
||||
fetchMock.getOnce(SPECIFIC_BRANCH_URL, 500);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_CHANGESETS_PENDING,
|
||||
itemId,
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchChangesets(repository, branch)).then(() => {
|
||||
expect(store.getActions()[0]).toEqual(expectedActions[0]);
|
||||
expect(store.getActions()[1].type).toEqual(FETCH_CHANGESETS_FAILURE);
|
||||
expect(store.getActions()[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fetch changesets by page', () => {
|
||||
fetchMock.getOnce(DEFAULT_BRANCH_URL + '?page=4', '{}');
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_CHANGESETS_PENDING,
|
||||
itemId: 'foo/bar',
|
||||
},
|
||||
{
|
||||
type: FETCH_CHANGESETS_SUCCESS,
|
||||
payload: {
|
||||
repository,
|
||||
undefined,
|
||||
changesets,
|
||||
},
|
||||
itemId: 'foo/bar',
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(fetchChangesets(repository, undefined, 5))
|
||||
.then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fetch changesets by branch and page', () => {
|
||||
fetchMock.getOnce(SPECIFIC_BRANCH_URL + '?page=4', '{}');
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_CHANGESETS_PENDING,
|
||||
itemId: 'foo/bar/specific',
|
||||
},
|
||||
{
|
||||
type: FETCH_CHANGESETS_SUCCESS,
|
||||
payload: {
|
||||
repository,
|
||||
branch,
|
||||
changesets,
|
||||
},
|
||||
itemId: 'foo/bar/specific',
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchChangesets(repository, branch, 5)).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('changesets reducer', () => {
|
||||
const responseBody = {
|
||||
page: 1,
|
||||
pageTotal: 10,
|
||||
_links: {},
|
||||
_embedded: {
|
||||
changesets: [
|
||||
{
|
||||
id: 'changeset1',
|
||||
author: {
|
||||
mail: 'z@phod.com',
|
||||
name: 'zaphod',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'changeset2',
|
||||
description: 'foo',
|
||||
},
|
||||
{
|
||||
id: 'changeset3',
|
||||
description: 'bar',
|
||||
},
|
||||
],
|
||||
_embedded: {
|
||||
tags: [],
|
||||
branches: [],
|
||||
parents: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
it('should set state to received changesets', () => {
|
||||
const newState = reducer(
|
||||
{},
|
||||
fetchChangesetsSuccess(repository, undefined, responseBody),
|
||||
);
|
||||
expect(newState).toBeDefined();
|
||||
expect(newState['foo/bar'].byId['changeset1'].author.mail).toEqual(
|
||||
'z@phod.com',
|
||||
);
|
||||
expect(newState['foo/bar'].byId['changeset2'].description).toEqual('foo');
|
||||
expect(newState['foo/bar'].byId['changeset3'].description).toEqual('bar');
|
||||
expect(newState['foo/bar'].byBranch['']).toEqual({
|
||||
entry: {
|
||||
page: 1,
|
||||
pageTotal: 10,
|
||||
_links: {},
|
||||
},
|
||||
entries: ['changeset1', 'changeset2', 'changeset3'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should store the changeset list to branch', () => {
|
||||
const newState = reducer(
|
||||
{},
|
||||
fetchChangesetsSuccess(repository, branch, responseBody),
|
||||
);
|
||||
|
||||
expect(newState['foo/bar'].byId['changeset1']).toBeDefined();
|
||||
expect(newState['foo/bar'].byBranch['specific'].entries).toEqual([
|
||||
'changeset1',
|
||||
'changeset2',
|
||||
'changeset3',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not remove existing changesets', () => {
|
||||
const state = {
|
||||
'foo/bar': {
|
||||
byId: {
|
||||
id2: {
|
||||
id: 'id2',
|
||||
},
|
||||
id1: {
|
||||
id: 'id1',
|
||||
},
|
||||
},
|
||||
byBranch: {
|
||||
'': {
|
||||
entries: ['id1', 'id2'],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const newState = reducer(
|
||||
state,
|
||||
fetchChangesetsSuccess(repository, undefined, responseBody),
|
||||
);
|
||||
|
||||
const fooBar = newState['foo/bar'];
|
||||
|
||||
expect(fooBar.byBranch[''].entries).toEqual([
|
||||
'changeset1',
|
||||
'changeset2',
|
||||
'changeset3',
|
||||
]);
|
||||
expect(fooBar.byId['id2']).toEqual({
|
||||
id: 'id2',
|
||||
});
|
||||
expect(fooBar.byId['id1']).toEqual({
|
||||
id: 'id1',
|
||||
});
|
||||
});
|
||||
|
||||
const responseBodySingleChangeset = {
|
||||
id: 'id3',
|
||||
author: {
|
||||
mail: 'z@phod.com',
|
||||
name: 'zaphod',
|
||||
},
|
||||
date: '2018-09-13T08:46:22Z',
|
||||
description: 'added testChangeset',
|
||||
_links: {},
|
||||
_embedded: {
|
||||
tags: [],
|
||||
branches: [],
|
||||
},
|
||||
};
|
||||
|
||||
it('should add changeset to state', () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
'foo/bar': {
|
||||
byId: {
|
||||
id2: {
|
||||
id: 'id2',
|
||||
author: {
|
||||
mail: 'mail@author.com',
|
||||
name: 'author',
|
||||
},
|
||||
},
|
||||
},
|
||||
list: {
|
||||
entry: {
|
||||
page: 1,
|
||||
pageTotal: 10,
|
||||
_links: {},
|
||||
},
|
||||
entries: ['id2'],
|
||||
},
|
||||
},
|
||||
},
|
||||
fetchChangesetSuccess(responseBodySingleChangeset, repository, 'id3'),
|
||||
);
|
||||
|
||||
expect(newState).toBeDefined();
|
||||
expect(newState['foo/bar'].byId['id3'].description).toEqual(
|
||||
'added testChangeset',
|
||||
);
|
||||
expect(newState['foo/bar'].byId['id3'].author.mail).toEqual('z@phod.com');
|
||||
expect(newState['foo/bar'].byId['id2']).toBeDefined();
|
||||
expect(newState['foo/bar'].byId['id3']).toBeDefined();
|
||||
expect(newState['foo/bar'].list).toEqual({
|
||||
entry: {
|
||||
page: 1,
|
||||
pageTotal: 10,
|
||||
_links: {},
|
||||
},
|
||||
entries: ['id2'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('changeset selectors', () => {
|
||||
const error = new Error('Something went wrong');
|
||||
|
||||
it('should return changeset', () => {
|
||||
const state = {
|
||||
changesets: {
|
||||
'foo/bar': {
|
||||
byId: {
|
||||
id1: {
|
||||
id: 'id1',
|
||||
},
|
||||
id2: {
|
||||
id: 'id2',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = getChangeset(state, repository, 'id1');
|
||||
expect(result).toEqual({
|
||||
id: 'id1',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return null if changeset does not exist', () => {
|
||||
const state = {
|
||||
changesets: {
|
||||
'foo/bar': {
|
||||
byId: {
|
||||
id1: {
|
||||
id: 'id1',
|
||||
},
|
||||
id2: {
|
||||
id: 'id2',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = getChangeset(state, repository, 'id3');
|
||||
expect(result).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return true if changeset does not exist', () => {
|
||||
const state = {
|
||||
changesets: {
|
||||
'foo/bar': {
|
||||
byId: {
|
||||
id1: {
|
||||
id: 'id1',
|
||||
},
|
||||
id2: {
|
||||
id: 'id2',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = shouldFetchChangeset(state, repository, 'id3');
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false if changeset exists', () => {
|
||||
const state = {
|
||||
changesets: {
|
||||
'foo/bar': {
|
||||
byId: {
|
||||
id1: {
|
||||
id: 'id1',
|
||||
},
|
||||
id2: {
|
||||
id: 'id2',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = shouldFetchChangeset(state, repository, 'id2');
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return true, when fetching changeset is pending', () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[FETCH_CHANGESET + '/foo/bar/id1']: true,
|
||||
},
|
||||
};
|
||||
|
||||
expect(isFetchChangesetPending(state, repository, 'id1')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return false, when fetching changeset is not pending', () => {
|
||||
expect(isFetchChangesetPending({}, repository, 'id1')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return error if fetching changeset failed', () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[FETCH_CHANGESET + '/foo/bar/id1']: error,
|
||||
},
|
||||
};
|
||||
|
||||
expect(getFetchChangesetFailure(state, repository, 'id1')).toEqual(error);
|
||||
});
|
||||
|
||||
it('should return false if fetching changeset did not fail', () => {
|
||||
expect(getFetchChangesetFailure({}, repository, 'id1')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should get all changesets for a given repository', () => {
|
||||
const state = {
|
||||
changesets: {
|
||||
'foo/bar': {
|
||||
byId: {
|
||||
id2: {
|
||||
id: 'id2',
|
||||
},
|
||||
id1: {
|
||||
id: 'id1',
|
||||
},
|
||||
},
|
||||
byBranch: {
|
||||
'': {
|
||||
entries: ['id1', 'id2'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = getChangesets(state, repository);
|
||||
expect(result).toEqual([
|
||||
{
|
||||
id: 'id1',
|
||||
},
|
||||
{
|
||||
id: 'id2',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return true, when fetching changesets is pending', () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[FETCH_CHANGESETS + '/foo/bar']: true,
|
||||
},
|
||||
};
|
||||
|
||||
expect(isFetchChangesetsPending(state, repository)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return false, when fetching changesets is not pending', () => {
|
||||
expect(isFetchChangesetsPending({}, repository)).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return error if fetching changesets failed', () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[FETCH_CHANGESETS + '/foo/bar']: error,
|
||||
},
|
||||
};
|
||||
|
||||
expect(getFetchChangesetsFailure(state, repository)).toEqual(error);
|
||||
});
|
||||
|
||||
it('should return false if fetching changesets did not fail', () => {
|
||||
expect(getFetchChangesetsFailure({}, repository)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return list as collection for the default branch', () => {
|
||||
const state = {
|
||||
changesets: {
|
||||
'foo/bar': {
|
||||
byId: {
|
||||
id2: {
|
||||
id: 'id2',
|
||||
},
|
||||
id1: {
|
||||
id: 'id1',
|
||||
},
|
||||
},
|
||||
byBranch: {
|
||||
'': {
|
||||
entry: {
|
||||
page: 1,
|
||||
pageTotal: 10,
|
||||
_links: {},
|
||||
},
|
||||
entries: ['id1', 'id2'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const collection = selectListAsCollection(state, repository);
|
||||
expect(collection.page).toBe(1);
|
||||
expect(collection.pageTotal).toBe(10);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,26 +1,24 @@
|
||||
// @flow
|
||||
|
||||
import {
|
||||
FAILURE_SUFFIX,
|
||||
PENDING_SUFFIX,
|
||||
SUCCESS_SUFFIX
|
||||
} from "../../modules/types";
|
||||
import { apiClient, urls } from "@scm-manager/ui-components";
|
||||
import { isPending } from "../../modules/pending";
|
||||
import { getFailure } from "../../modules/failure";
|
||||
import type {
|
||||
SUCCESS_SUFFIX,
|
||||
} from '../../modules/types';
|
||||
import { apiClient, urls } from '@scm-manager/ui-components';
|
||||
import { isPending } from '../../modules/pending';
|
||||
import { getFailure } from '../../modules/failure';
|
||||
import {
|
||||
Action,
|
||||
Branch,
|
||||
PagedCollection,
|
||||
Repository
|
||||
} from "@scm-manager/ui-types";
|
||||
Repository,
|
||||
} from '@scm-manager/ui-types';
|
||||
|
||||
export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS";
|
||||
export const FETCH_CHANGESETS = 'scm/repos/FETCH_CHANGESETS';
|
||||
export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`;
|
||||
export const FETCH_CHANGESETS_SUCCESS = `${FETCH_CHANGESETS}_${SUCCESS_SUFFIX}`;
|
||||
export const FETCH_CHANGESETS_FAILURE = `${FETCH_CHANGESETS}_${FAILURE_SUFFIX}`;
|
||||
|
||||
export const FETCH_CHANGESET = "scm/repos/FETCH_CHANGESET";
|
||||
export const FETCH_CHANGESET = 'scm/repos/FETCH_CHANGESET';
|
||||
export const FETCH_CHANGESET_PENDING = `${FETCH_CHANGESET}_${PENDING_SUFFIX}`;
|
||||
export const FETCH_CHANGESET_SUCCESS = `${FETCH_CHANGESET}_${SUCCESS_SUFFIX}`;
|
||||
export const FETCH_CHANGESET_FAILURE = `${FETCH_CHANGESET}_${FAILURE_SUFFIX}`;
|
||||
@@ -55,46 +53,50 @@ function createChangesetUrl(repository: Repository, id: string) {
|
||||
|
||||
export function fetchChangesetPending(
|
||||
repository: Repository,
|
||||
id: string
|
||||
id: string,
|
||||
): Action {
|
||||
return {
|
||||
type: FETCH_CHANGESET_PENDING,
|
||||
itemId: createChangesetItemId(repository, id)
|
||||
itemId: createChangesetItemId(repository, id),
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchChangesetSuccess(
|
||||
changeset: any,
|
||||
repository: Repository,
|
||||
id: string
|
||||
id: string,
|
||||
): Action {
|
||||
return {
|
||||
type: FETCH_CHANGESET_SUCCESS,
|
||||
payload: { changeset, repository, id },
|
||||
itemId: createChangesetItemId(repository, id)
|
||||
payload: {
|
||||
changeset,
|
||||
repository,
|
||||
id,
|
||||
},
|
||||
itemId: createChangesetItemId(repository, id),
|
||||
};
|
||||
}
|
||||
|
||||
function fetchChangesetFailure(
|
||||
repository: Repository,
|
||||
id: string,
|
||||
error: Error
|
||||
error: Error,
|
||||
): Action {
|
||||
return {
|
||||
type: FETCH_CHANGESET_FAILURE,
|
||||
payload: {
|
||||
repository,
|
||||
id,
|
||||
error
|
||||
error,
|
||||
},
|
||||
itemId: createChangesetItemId(repository, id)
|
||||
itemId: createChangesetItemId(repository, id),
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchChangesets(
|
||||
repository: Repository,
|
||||
branch?: Branch,
|
||||
page?: number
|
||||
page?: number,
|
||||
) {
|
||||
const link = createChangesetsLink(repository, branch, page);
|
||||
|
||||
@@ -115,7 +117,7 @@ export function fetchChangesets(
|
||||
function createChangesetsLink(
|
||||
repository: Repository,
|
||||
branch?: Branch,
|
||||
page?: number
|
||||
page?: number,
|
||||
) {
|
||||
let link = repository._links.changesets.href;
|
||||
|
||||
@@ -131,58 +133,58 @@ function createChangesetsLink(
|
||||
|
||||
export function fetchChangesetsPending(
|
||||
repository: Repository,
|
||||
branch?: Branch
|
||||
branch?: Branch,
|
||||
): Action {
|
||||
const itemId = createItemId(repository, branch);
|
||||
|
||||
return {
|
||||
type: FETCH_CHANGESETS_PENDING,
|
||||
itemId
|
||||
itemId,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchChangesetsSuccess(
|
||||
repository: Repository,
|
||||
branch?: Branch,
|
||||
changesets: any
|
||||
changesets: any,
|
||||
): Action {
|
||||
return {
|
||||
type: FETCH_CHANGESETS_SUCCESS,
|
||||
payload: {
|
||||
repository,
|
||||
branch,
|
||||
changesets
|
||||
changesets,
|
||||
},
|
||||
itemId: createItemId(repository, branch)
|
||||
itemId: createItemId(repository, branch),
|
||||
};
|
||||
}
|
||||
|
||||
function fetchChangesetsFailure(
|
||||
repository: Repository,
|
||||
branch?: Branch,
|
||||
error: Error
|
||||
error: Error,
|
||||
): Action {
|
||||
return {
|
||||
type: FETCH_CHANGESETS_FAILURE,
|
||||
payload: {
|
||||
repository,
|
||||
error,
|
||||
branch
|
||||
branch,
|
||||
},
|
||||
itemId: createItemId(repository, branch)
|
||||
itemId: createItemId(repository, branch),
|
||||
};
|
||||
}
|
||||
|
||||
function createChangesetItemId(repository: Repository, id: string) {
|
||||
const { namespace, name } = repository;
|
||||
return namespace + "/" + name + "/" + id;
|
||||
return namespace + '/' + name + '/' + id;
|
||||
}
|
||||
|
||||
function createItemId(repository: Repository, branch?: Branch): string {
|
||||
const { namespace, name } = repository;
|
||||
let itemId = namespace + "/" + name;
|
||||
let itemId = namespace + '/' + name;
|
||||
if (branch) {
|
||||
itemId = itemId + "/" + branch.name;
|
||||
itemId = itemId + '/' + branch.name;
|
||||
}
|
||||
return itemId;
|
||||
}
|
||||
@@ -190,8 +192,10 @@ function createItemId(repository: Repository, branch?: Branch): string {
|
||||
// reducer
|
||||
export default function reducer(
|
||||
state: any = {},
|
||||
action: Action = { type: "UNKNOWN" }
|
||||
): Object {
|
||||
action: Action = {
|
||||
type: 'UNKNOWN',
|
||||
},
|
||||
): object {
|
||||
if (!action.payload) {
|
||||
return state;
|
||||
}
|
||||
@@ -214,9 +218,9 @@ export default function reducer(
|
||||
...state[_key],
|
||||
byId: {
|
||||
..._oldByIds,
|
||||
[changeset.id]: changeset
|
||||
}
|
||||
}
|
||||
[changeset.id]: changeset,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
case FETCH_CHANGESETS_SUCCESS:
|
||||
@@ -235,7 +239,7 @@ export default function reducer(
|
||||
oldState = state[repoId];
|
||||
}
|
||||
|
||||
const branchName = payload.branch ? payload.branch.name : "";
|
||||
const branchName = payload.branch ? payload.branch.name : '';
|
||||
const byIds = extractChangesetsByIds(changesets);
|
||||
|
||||
return {
|
||||
@@ -243,7 +247,7 @@ export default function reducer(
|
||||
[repoId]: {
|
||||
byId: {
|
||||
...oldState.byId,
|
||||
...byIds
|
||||
...byIds,
|
||||
},
|
||||
byBranch: {
|
||||
...oldState.byBranch,
|
||||
@@ -252,11 +256,11 @@ export default function reducer(
|
||||
entry: {
|
||||
page: payload.changesets.page,
|
||||
pageTotal: payload.changesets.pageTotal,
|
||||
_links: payload.changesets._links
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_links: payload.changesets._links,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
@@ -275,9 +279,9 @@ function extractChangesetsByIds(changesets: any) {
|
||||
|
||||
//selectors
|
||||
export function getChangesets(
|
||||
state: Object,
|
||||
state: object,
|
||||
repository: Repository,
|
||||
branch?: Branch
|
||||
branch?: Branch,
|
||||
) {
|
||||
const repoKey = createItemId(repository);
|
||||
|
||||
@@ -286,7 +290,7 @@ export function getChangesets(
|
||||
return null;
|
||||
}
|
||||
|
||||
const branchName = branch ? branch.name : "";
|
||||
const branchName = branch ? branch.name : '';
|
||||
|
||||
const changesets = stateRoot.byBranch[branchName];
|
||||
if (!changesets) {
|
||||
@@ -299,9 +303,9 @@ export function getChangesets(
|
||||
}
|
||||
|
||||
export function getChangeset(
|
||||
state: Object,
|
||||
state: object,
|
||||
repository: Repository,
|
||||
id: string
|
||||
id: string,
|
||||
) {
|
||||
const key = createItemId(repository);
|
||||
const changesets =
|
||||
@@ -315,9 +319,9 @@ export function getChangeset(
|
||||
}
|
||||
|
||||
export function shouldFetchChangeset(
|
||||
state: Object,
|
||||
state: object,
|
||||
repository: Repository,
|
||||
id: string
|
||||
id: string,
|
||||
) {
|
||||
if (getChangeset(state, repository, id)) {
|
||||
return false;
|
||||
@@ -326,49 +330,49 @@ export function shouldFetchChangeset(
|
||||
}
|
||||
|
||||
export function isFetchChangesetPending(
|
||||
state: Object,
|
||||
state: object,
|
||||
repository: Repository,
|
||||
id: string
|
||||
id: string,
|
||||
) {
|
||||
return isPending(
|
||||
state,
|
||||
FETCH_CHANGESET,
|
||||
createChangesetItemId(repository, id)
|
||||
createChangesetItemId(repository, id),
|
||||
);
|
||||
}
|
||||
|
||||
export function getFetchChangesetFailure(
|
||||
state: Object,
|
||||
state: object,
|
||||
repository: Repository,
|
||||
id: string
|
||||
id: string,
|
||||
) {
|
||||
return getFailure(
|
||||
state,
|
||||
FETCH_CHANGESET,
|
||||
createChangesetItemId(repository, id)
|
||||
createChangesetItemId(repository, id),
|
||||
);
|
||||
}
|
||||
|
||||
export function isFetchChangesetsPending(
|
||||
state: Object,
|
||||
state: object,
|
||||
repository: Repository,
|
||||
branch?: Branch
|
||||
branch?: Branch,
|
||||
) {
|
||||
return isPending(state, FETCH_CHANGESETS, createItemId(repository, branch));
|
||||
}
|
||||
|
||||
export function getFetchChangesetsFailure(
|
||||
state: Object,
|
||||
state: object,
|
||||
repository: Repository,
|
||||
branch?: Branch
|
||||
branch?: Branch,
|
||||
) {
|
||||
return getFailure(state, FETCH_CHANGESETS, createItemId(repository, branch));
|
||||
}
|
||||
|
||||
const selectList = (state: Object, repository: Repository, branch?: Branch) => {
|
||||
const selectList = (state: object, repository: Repository, branch?: Branch) => {
|
||||
const repoId = createItemId(repository);
|
||||
|
||||
const branchName = branch ? branch.name : "";
|
||||
const branchName = branch ? branch.name : '';
|
||||
if (state.changesets[repoId]) {
|
||||
const repoState = state.changesets[repoId];
|
||||
|
||||
@@ -380,10 +384,10 @@ const selectList = (state: Object, repository: Repository, branch?: Branch) => {
|
||||
};
|
||||
|
||||
const selectListEntry = (
|
||||
state: Object,
|
||||
state: object,
|
||||
repository: Repository,
|
||||
branch?: Branch
|
||||
): Object => {
|
||||
branch?: Branch,
|
||||
): object => {
|
||||
const list = selectList(state, repository, branch);
|
||||
if (list.entry) {
|
||||
return list.entry;
|
||||
@@ -392,9 +396,9 @@ const selectListEntry = (
|
||||
};
|
||||
|
||||
export const selectListAsCollection = (
|
||||
state: Object,
|
||||
state: object,
|
||||
repository: Repository,
|
||||
branch?: Branch
|
||||
branch?: Branch,
|
||||
): PagedCollection => {
|
||||
return selectListEntry(state, repository, branch);
|
||||
};
|
||||
@@ -1,858 +0,0 @@
|
||||
// @flow
|
||||
import configureMockStore from "redux-mock-store";
|
||||
import thunk from "redux-thunk";
|
||||
import fetchMock from "fetch-mock";
|
||||
import reducer, {
|
||||
FETCH_REPOS_PENDING,
|
||||
FETCH_REPOS_SUCCESS,
|
||||
fetchRepos,
|
||||
FETCH_REPOS_FAILURE,
|
||||
fetchReposSuccess,
|
||||
getRepositoryCollection,
|
||||
FETCH_REPOS,
|
||||
isFetchReposPending,
|
||||
getFetchReposFailure,
|
||||
fetchReposByLink,
|
||||
fetchReposByPage,
|
||||
FETCH_REPO,
|
||||
fetchRepoByLink,
|
||||
fetchRepoByName,
|
||||
FETCH_REPO_PENDING,
|
||||
FETCH_REPO_SUCCESS,
|
||||
FETCH_REPO_FAILURE,
|
||||
fetchRepoSuccess,
|
||||
getRepository,
|
||||
isFetchRepoPending,
|
||||
getFetchRepoFailure,
|
||||
CREATE_REPO_PENDING,
|
||||
CREATE_REPO_SUCCESS,
|
||||
createRepo,
|
||||
CREATE_REPO_FAILURE,
|
||||
isCreateRepoPending,
|
||||
CREATE_REPO,
|
||||
getCreateRepoFailure,
|
||||
isAbleToCreateRepos,
|
||||
DELETE_REPO,
|
||||
DELETE_REPO_SUCCESS,
|
||||
deleteRepo,
|
||||
DELETE_REPO_PENDING,
|
||||
DELETE_REPO_FAILURE,
|
||||
isDeleteRepoPending,
|
||||
getDeleteRepoFailure,
|
||||
modifyRepo,
|
||||
MODIFY_REPO_PENDING,
|
||||
MODIFY_REPO_SUCCESS,
|
||||
MODIFY_REPO_FAILURE,
|
||||
MODIFY_REPO,
|
||||
isModifyRepoPending,
|
||||
getModifyRepoFailure,
|
||||
getPermissionsLink
|
||||
} from "./repos";
|
||||
import type { Repository, RepositoryCollection } from "@scm-manager/ui-types";
|
||||
|
||||
const hitchhikerPuzzle42: Repository = {
|
||||
contact: "fourtytwo@hitchhiker.com",
|
||||
creationDate: "2018-07-31T08:58:45.961Z",
|
||||
description: "the answer to life the universe and everything",
|
||||
namespace: "hitchhiker",
|
||||
name: "puzzle42",
|
||||
type: "svn",
|
||||
_links: {
|
||||
self: {
|
||||
href: "http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42"
|
||||
},
|
||||
delete: {
|
||||
href: "http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42"
|
||||
},
|
||||
update: {
|
||||
href: "http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42"
|
||||
},
|
||||
permissions: {
|
||||
href:
|
||||
"http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42/permissions/"
|
||||
},
|
||||
tags: {
|
||||
href:
|
||||
"http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42/tags/"
|
||||
},
|
||||
branches: {
|
||||
href:
|
||||
"http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42/branches/"
|
||||
},
|
||||
changesets: {
|
||||
href:
|
||||
"http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42/changesets/"
|
||||
},
|
||||
sources: {
|
||||
href:
|
||||
"http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42/sources/"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const hitchhikerRestatend: Repository = {
|
||||
contact: "restatend@hitchhiker.com",
|
||||
creationDate: "2018-07-31T08:58:32.803Z",
|
||||
description: "restaurant at the end of the universe",
|
||||
namespace: "hitchhiker",
|
||||
name: "restatend",
|
||||
type: "git",
|
||||
_links: {
|
||||
self: {
|
||||
href: "http://localhost:8081/api/v2/repositories/hitchhiker/restatend"
|
||||
},
|
||||
delete: {
|
||||
href: "http://localhost:8081/api/v2/repositories/hitchhiker/restatend"
|
||||
},
|
||||
update: {
|
||||
href: "http://localhost:8081/api/v2/repositories/hitchhiker/restatend"
|
||||
},
|
||||
permissions: {
|
||||
href:
|
||||
"http://localhost:8081/api/v2/repositories/hitchhiker/restatend/permissions/"
|
||||
},
|
||||
tags: {
|
||||
href:
|
||||
"http://localhost:8081/api/v2/repositories/hitchhiker/restatend/tags/"
|
||||
},
|
||||
branches: {
|
||||
href:
|
||||
"http://localhost:8081/api/v2/repositories/hitchhiker/restatend/branches/"
|
||||
},
|
||||
changesets: {
|
||||
href:
|
||||
"http://localhost:8081/api/v2/repositories/hitchhiker/restatend/changesets/"
|
||||
},
|
||||
sources: {
|
||||
href:
|
||||
"http://localhost:8081/api/v2/repositories/hitchhiker/restatend/sources/"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const slartiFjords: Repository = {
|
||||
contact: "slartibartfast@hitchhiker.com",
|
||||
description: "My award-winning fjords from the Norwegian coast",
|
||||
namespace: "slarti",
|
||||
name: "fjords",
|
||||
type: "hg",
|
||||
creationDate: "2018-07-31T08:59:05.653Z",
|
||||
_links: {
|
||||
self: {
|
||||
href: "http://localhost:8081/api/v2/repositories/slarti/fjords"
|
||||
},
|
||||
delete: {
|
||||
href: "http://localhost:8081/api/v2/repositories/slarti/fjords"
|
||||
},
|
||||
update: {
|
||||
href: "http://localhost:8081/api/v2/repositories/slarti/fjords"
|
||||
},
|
||||
permissions: {
|
||||
href:
|
||||
"http://localhost:8081/api/v2/repositories/slarti/fjords/permissions/"
|
||||
},
|
||||
tags: {
|
||||
href: "http://localhost:8081/api/v2/repositories/slarti/fjords/tags/"
|
||||
},
|
||||
branches: {
|
||||
href: "http://localhost:8081/api/v2/repositories/slarti/fjords/branches/"
|
||||
},
|
||||
changesets: {
|
||||
href:
|
||||
"http://localhost:8081/api/v2/repositories/slarti/fjords/changesets/"
|
||||
},
|
||||
sources: {
|
||||
href: "http://localhost:8081/api/v2/repositories/slarti/fjords/sources/"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const repositoryCollection: RepositoryCollection = {
|
||||
page: 0,
|
||||
pageTotal: 1,
|
||||
_links: {
|
||||
self: {
|
||||
href: "http://localhost:8081/api/v2/repositories/?page=0&pageSize=10"
|
||||
},
|
||||
first: {
|
||||
href: "http://localhost:8081/api/v2/repositories/?page=0&pageSize=10"
|
||||
},
|
||||
last: {
|
||||
href: "http://localhost:8081/api/v2/repositories/?page=0&pageSize=10"
|
||||
},
|
||||
create: {
|
||||
href: "http://localhost:8081/api/v2/repositories/"
|
||||
}
|
||||
},
|
||||
_embedded: {
|
||||
repositories: [hitchhikerPuzzle42, hitchhikerRestatend, slartiFjords]
|
||||
}
|
||||
};
|
||||
|
||||
const repositoryCollectionWithNames: RepositoryCollection = {
|
||||
page: 0,
|
||||
pageTotal: 1,
|
||||
_links: {
|
||||
self: {
|
||||
href: "http://localhost:8081/api/v2/repositories/?page=0&pageSize=10"
|
||||
},
|
||||
first: {
|
||||
href: "http://localhost:8081/api/v2/repositories/?page=0&pageSize=10"
|
||||
},
|
||||
last: {
|
||||
href: "http://localhost:8081/api/v2/repositories/?page=0&pageSize=10"
|
||||
},
|
||||
create: {
|
||||
href: "http://localhost:8081/api/v2/repositories/"
|
||||
}
|
||||
},
|
||||
_embedded: {
|
||||
repositories: [
|
||||
"hitchhiker/puzzle42",
|
||||
"hitchhiker/restatend",
|
||||
"slarti/fjords"
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
describe("repos fetch", () => {
|
||||
const URL = "repositories";
|
||||
const REPOS_URL = "/api/v2/repositories";
|
||||
const SORT = "sortBy=namespaceAndName";
|
||||
const REPOS_URL_WITH_SORT = REPOS_URL + "?" + SORT;
|
||||
const mockStore = configureMockStore([thunk]);
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it("should successfully fetch repos", () => {
|
||||
fetchMock.getOnce(REPOS_URL_WITH_SORT, repositoryCollection);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: FETCH_REPOS_PENDING },
|
||||
{
|
||||
type: FETCH_REPOS_SUCCESS,
|
||||
payload: repositoryCollection
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchRepos(URL)).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should successfully fetch page 42", () => {
|
||||
const url = REPOS_URL + "?page=42&" + SORT;
|
||||
fetchMock.getOnce(url, repositoryCollection);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: FETCH_REPOS_PENDING },
|
||||
{
|
||||
type: FETCH_REPOS_SUCCESS,
|
||||
payload: repositoryCollection
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store.dispatch(fetchReposByPage(URL, 43)).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should successfully fetch repos from link", () => {
|
||||
fetchMock.getOnce(
|
||||
REPOS_URL + "?" + SORT + "&page=42",
|
||||
repositoryCollection
|
||||
);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: FETCH_REPOS_PENDING },
|
||||
{
|
||||
type: FETCH_REPOS_SUCCESS,
|
||||
payload: repositoryCollection
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(
|
||||
fetchReposByLink("/repositories?sortBy=namespaceAndName&page=42")
|
||||
)
|
||||
.then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should append sortby parameter and successfully fetch repos from link", () => {
|
||||
fetchMock.getOnce(
|
||||
"/api/v2/repositories?one=1&sortBy=namespaceAndName",
|
||||
repositoryCollection
|
||||
);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: FETCH_REPOS_PENDING },
|
||||
{
|
||||
type: FETCH_REPOS_SUCCESS,
|
||||
payload: repositoryCollection
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store.dispatch(fetchReposByLink("/repositories?one=1")).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch FETCH_REPOS_FAILURE, it the request fails", () => {
|
||||
fetchMock.getOnce(REPOS_URL_WITH_SORT, {
|
||||
status: 500
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchRepos(URL)).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(FETCH_REPOS_PENDING);
|
||||
expect(actions[1].type).toEqual(FETCH_REPOS_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("should successfully fetch repo slarti/fjords by name", () => {
|
||||
fetchMock.getOnce(REPOS_URL + "/slarti/fjords", slartiFjords);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_REPO_PENDING,
|
||||
payload: {
|
||||
namespace: "slarti",
|
||||
name: "fjords"
|
||||
},
|
||||
itemId: "slarti/fjords"
|
||||
},
|
||||
{
|
||||
type: FETCH_REPO_SUCCESS,
|
||||
payload: slartiFjords,
|
||||
itemId: "slarti/fjords"
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchRepoByName(URL, "slarti", "fjords")).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch FETCH_REPO_FAILURE, if the request for slarti/fjords by name fails", () => {
|
||||
fetchMock.getOnce(REPOS_URL + "/slarti/fjords", {
|
||||
status: 500
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchRepoByName(URL, "slarti", "fjords")).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(FETCH_REPO_PENDING);
|
||||
expect(actions[1].type).toEqual(FETCH_REPO_FAILURE);
|
||||
expect(actions[1].payload.namespace).toBe("slarti");
|
||||
expect(actions[1].payload.name).toBe("fjords");
|
||||
expect(actions[1].payload.error).toBeDefined();
|
||||
expect(actions[1].itemId).toBe("slarti/fjords");
|
||||
});
|
||||
});
|
||||
|
||||
it("should successfully fetch repo slarti/fjords", () => {
|
||||
fetchMock.getOnce(
|
||||
"http://localhost:8081/api/v2/repositories/slarti/fjords",
|
||||
slartiFjords
|
||||
);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_REPO_PENDING,
|
||||
payload: {
|
||||
namespace: "slarti",
|
||||
name: "fjords"
|
||||
},
|
||||
itemId: "slarti/fjords"
|
||||
},
|
||||
{
|
||||
type: FETCH_REPO_SUCCESS,
|
||||
payload: slartiFjords,
|
||||
itemId: "slarti/fjords"
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchRepoByLink(slartiFjords)).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch FETCH_REPO_FAILURE, it the request for slarti/fjords fails", () => {
|
||||
fetchMock.getOnce(
|
||||
"http://localhost:8081/api/v2/repositories/slarti/fjords",
|
||||
{
|
||||
status: 500
|
||||
}
|
||||
);
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchRepoByLink(slartiFjords)).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(FETCH_REPO_PENDING);
|
||||
expect(actions[1].type).toEqual(FETCH_REPO_FAILURE);
|
||||
expect(actions[1].payload.namespace).toBe("slarti");
|
||||
expect(actions[1].payload.name).toBe("fjords");
|
||||
expect(actions[1].payload.error).toBeDefined();
|
||||
expect(actions[1].itemId).toBe("slarti/fjords");
|
||||
});
|
||||
});
|
||||
|
||||
it("should successfully create repo slarti/fjords", () => {
|
||||
fetchMock.postOnce(REPOS_URL, {
|
||||
status: 201,
|
||||
headers: {
|
||||
location: "repositories/slarti/fjords"
|
||||
}
|
||||
});
|
||||
|
||||
fetchMock.getOnce(REPOS_URL + "/slarti/fjords", slartiFjords);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: CREATE_REPO_PENDING
|
||||
},
|
||||
{
|
||||
type: CREATE_REPO_SUCCESS
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(createRepo(URL, slartiFjords)).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should successfully create repo slarti/fjords and call the callback", () => {
|
||||
fetchMock.postOnce(REPOS_URL, {
|
||||
status: 201,
|
||||
headers: {
|
||||
location: "repositories/slarti/fjords"
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
fetchMock.getOnce(REPOS_URL + "/slarti/fjords", slartiFjords);
|
||||
|
||||
let callMe = "not yet";
|
||||
|
||||
const callback = (r: any) => {
|
||||
expect(r).toEqual(slartiFjords);
|
||||
callMe = "yeah";
|
||||
};
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(createRepo(URL, slartiFjords, callback)).then(() => {
|
||||
expect(callMe).toBe("yeah");
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch failure if server returns status code 500", () => {
|
||||
fetchMock.postOnce(REPOS_URL, {
|
||||
status: 500
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(createRepo(URL, slartiFjords)).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(CREATE_REPO_PENDING);
|
||||
expect(actions[1].type).toEqual(CREATE_REPO_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("should successfully delete repo slarti/fjords", () => {
|
||||
fetchMock.delete(
|
||||
"http://localhost:8081/api/v2/repositories/slarti/fjords",
|
||||
{
|
||||
status: 204
|
||||
}
|
||||
);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: DELETE_REPO_PENDING,
|
||||
payload: slartiFjords,
|
||||
itemId: "slarti/fjords"
|
||||
},
|
||||
{
|
||||
type: DELETE_REPO_SUCCESS,
|
||||
payload: slartiFjords,
|
||||
itemId: "slarti/fjords"
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(deleteRepo(slartiFjords)).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should successfully delete repo slarti/fjords and call the callback", () => {
|
||||
fetchMock.delete(
|
||||
"http://localhost:8081/api/v2/repositories/slarti/fjords",
|
||||
{
|
||||
status: 204
|
||||
}
|
||||
);
|
||||
|
||||
let callMe = "not yet";
|
||||
|
||||
const callback = () => {
|
||||
callMe = "yeah";
|
||||
};
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(deleteRepo(slartiFjords, callback)).then(() => {
|
||||
expect(callMe).toBe("yeah");
|
||||
});
|
||||
});
|
||||
|
||||
it("should disapatch failure on delete, if server returns status code 500", () => {
|
||||
fetchMock.delete(
|
||||
"http://localhost:8081/api/v2/repositories/slarti/fjords",
|
||||
{
|
||||
status: 500
|
||||
}
|
||||
);
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(deleteRepo(slartiFjords)).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(DELETE_REPO_PENDING);
|
||||
expect(actions[1].type).toEqual(DELETE_REPO_FAILURE);
|
||||
expect(actions[1].payload.repository).toBe(slartiFjords);
|
||||
expect(actions[1].payload.error).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("should successfully modify slarti/fjords repo", () => {
|
||||
fetchMock.putOnce(slartiFjords._links.update.href, {
|
||||
status: 204
|
||||
});
|
||||
fetchMock.getOnce(
|
||||
"http://localhost:8081/api/v2/repositories/slarti/fjords",
|
||||
{
|
||||
status: 500
|
||||
}
|
||||
);
|
||||
|
||||
let editedFjords = { ...slartiFjords };
|
||||
editedFjords.description = "coast of africa";
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store.dispatch(modifyRepo(editedFjords)).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(MODIFY_REPO_PENDING);
|
||||
expect(actions[1].type).toEqual(MODIFY_REPO_SUCCESS);
|
||||
expect(actions[2].type).toEqual(FETCH_REPO_PENDING);
|
||||
});
|
||||
});
|
||||
|
||||
it("should successfully modify slarti/fjords repo and call the callback", () => {
|
||||
fetchMock.putOnce(slartiFjords._links.update.href, {
|
||||
status: 204
|
||||
});
|
||||
fetchMock.getOnce(
|
||||
"http://localhost:8081/api/v2/repositories/slarti/fjords",
|
||||
{
|
||||
status: 500
|
||||
}
|
||||
);
|
||||
|
||||
let editedFjords = { ...slartiFjords };
|
||||
editedFjords.description = "coast of africa";
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
let called = false;
|
||||
const callback = () => {
|
||||
called = true;
|
||||
};
|
||||
|
||||
return store.dispatch(modifyRepo(editedFjords, callback)).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(MODIFY_REPO_PENDING);
|
||||
expect(actions[1].type).toEqual(MODIFY_REPO_SUCCESS);
|
||||
expect(actions[2].type).toEqual(FETCH_REPO_PENDING);
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail modifying on HTTP 500", () => {
|
||||
fetchMock.putOnce(slartiFjords._links.update.href, {
|
||||
status: 500
|
||||
});
|
||||
|
||||
let editedFjords = { ...slartiFjords };
|
||||
editedFjords.description = "coast of africa";
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store.dispatch(modifyRepo(editedFjords)).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(MODIFY_REPO_PENDING);
|
||||
expect(actions[1].type).toEqual(MODIFY_REPO_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("repos 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 repositories by it's namespace and name on FETCH_REPOS_SUCCESS", () => {
|
||||
const newState = reducer({}, fetchReposSuccess(repositoryCollection));
|
||||
expect(newState.list.page).toBe(0);
|
||||
expect(newState.list.pageTotal).toBe(1);
|
||||
expect(newState.list._embedded.repositories).toEqual([
|
||||
"hitchhiker/puzzle42",
|
||||
"hitchhiker/restatend",
|
||||
"slarti/fjords"
|
||||
]);
|
||||
|
||||
expect(newState.byNames["hitchhiker/puzzle42"]).toBe(hitchhikerPuzzle42);
|
||||
expect(newState.byNames["hitchhiker/restatend"]).toBe(hitchhikerRestatend);
|
||||
expect(newState.byNames["slarti/fjords"]).toBe(slartiFjords);
|
||||
});
|
||||
|
||||
it("should store the repo at byNames", () => {
|
||||
const newState = reducer({}, fetchRepoSuccess(slartiFjords));
|
||||
expect(newState.byNames["slarti/fjords"]).toBe(slartiFjords);
|
||||
});
|
||||
});
|
||||
|
||||
describe("repos selectors", () => {
|
||||
const error = new Error("something goes wrong");
|
||||
|
||||
it("should return the repositories collection", () => {
|
||||
const state = {
|
||||
repos: {
|
||||
list: repositoryCollectionWithNames,
|
||||
byNames: {
|
||||
"hitchhiker/puzzle42": hitchhikerPuzzle42,
|
||||
"hitchhiker/restatend": hitchhikerRestatend,
|
||||
"slarti/fjords": slartiFjords
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const collection = getRepositoryCollection(state);
|
||||
expect(collection).toEqual(repositoryCollection);
|
||||
});
|
||||
|
||||
it("should return true, when fetch repos is pending", () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[FETCH_REPOS]: true
|
||||
}
|
||||
};
|
||||
expect(isFetchReposPending(state)).toEqual(true);
|
||||
});
|
||||
|
||||
it("should return false, when fetch repos is not pending", () => {
|
||||
expect(isFetchReposPending({})).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return error when fetch repos did fail", () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[FETCH_REPOS]: error
|
||||
}
|
||||
};
|
||||
expect(getFetchReposFailure(state)).toEqual(error);
|
||||
});
|
||||
|
||||
it("should return undefined when fetch repos did not fail", () => {
|
||||
expect(getFetchReposFailure({})).toBe(undefined);
|
||||
});
|
||||
|
||||
it("should return the repository collection", () => {
|
||||
const state = {
|
||||
repos: {
|
||||
byNames: {
|
||||
"slarti/fjords": slartiFjords
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const repository = getRepository(state, "slarti", "fjords");
|
||||
expect(repository).toEqual(slartiFjords);
|
||||
});
|
||||
|
||||
it("should return permissions link", () => {
|
||||
const state = {
|
||||
repos: {
|
||||
byNames: {
|
||||
"slarti/fjords": slartiFjords
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const link = getPermissionsLink(state, "slarti", "fjords");
|
||||
expect(link).toEqual(
|
||||
"http://localhost:8081/api/v2/repositories/slarti/fjords/permissions/"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return true, when fetch repo is pending", () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[FETCH_REPO + "/slarti/fjords"]: true
|
||||
}
|
||||
};
|
||||
expect(isFetchRepoPending(state, "slarti", "fjords")).toEqual(true);
|
||||
});
|
||||
|
||||
it("should return false, when fetch repo is not pending", () => {
|
||||
expect(isFetchRepoPending({}, "slarti", "fjords")).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return error when fetch repo did fail", () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[FETCH_REPO + "/slarti/fjords"]: error
|
||||
}
|
||||
};
|
||||
expect(getFetchRepoFailure(state, "slarti", "fjords")).toEqual(error);
|
||||
});
|
||||
|
||||
it("should return undefined when fetch repo did not fail", () => {
|
||||
expect(getFetchRepoFailure({}, "slarti", "fjords")).toBe(undefined);
|
||||
});
|
||||
|
||||
// create
|
||||
|
||||
it("should return true, when create repo is pending", () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[CREATE_REPO]: true
|
||||
}
|
||||
};
|
||||
expect(isCreateRepoPending(state)).toEqual(true);
|
||||
});
|
||||
|
||||
it("should return false, when create repo is not pending", () => {
|
||||
expect(isCreateRepoPending({})).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return error when create repo did fail", () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[CREATE_REPO]: error
|
||||
}
|
||||
};
|
||||
expect(getCreateRepoFailure(state)).toEqual(error);
|
||||
});
|
||||
|
||||
it("should return undefined when create repo did not fail", () => {
|
||||
expect(getCreateRepoFailure({})).toBe(undefined);
|
||||
});
|
||||
|
||||
// modify
|
||||
|
||||
it("should return true, when modify repo is pending", () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[MODIFY_REPO + "/slarti/fjords"]: true
|
||||
}
|
||||
};
|
||||
|
||||
expect(isModifyRepoPending(state, "slarti", "fjords")).toEqual(true);
|
||||
});
|
||||
|
||||
it("should return false, when modify repo is not pending", () => {
|
||||
expect(isModifyRepoPending({}, "slarti", "fjords")).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return error, when modify repo failed", () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[MODIFY_REPO + "/slarti/fjords"]: error
|
||||
}
|
||||
};
|
||||
|
||||
expect(getModifyRepoFailure(state, "slarti", "fjords")).toEqual(error);
|
||||
});
|
||||
|
||||
it("should return undefined, when modify did not fail", () => {
|
||||
expect(getModifyRepoFailure({}, "slarti", "fjords")).toBeUndefined();
|
||||
});
|
||||
|
||||
// delete
|
||||
|
||||
it("should return true, when delete repo is pending", () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[DELETE_REPO + "/slarti/fjords"]: true
|
||||
}
|
||||
};
|
||||
expect(isDeleteRepoPending(state, "slarti", "fjords")).toEqual(true);
|
||||
});
|
||||
|
||||
it("should return false, when delete repo is not pending", () => {
|
||||
expect(isDeleteRepoPending({}, "slarti", "fjords")).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return error when delete repo did fail", () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[DELETE_REPO + "/slarti/fjords"]: error
|
||||
}
|
||||
};
|
||||
expect(getDeleteRepoFailure(state, "slarti", "fjords")).toEqual(error);
|
||||
});
|
||||
|
||||
it("should return undefined when delete repo did not fail", () => {
|
||||
expect(getDeleteRepoFailure({}, "slarti", "fjords")).toBe(undefined);
|
||||
});
|
||||
|
||||
it("should return true if the list contains the create link", () => {
|
||||
const state = {
|
||||
repos: {
|
||||
list: repositoryCollection
|
||||
}
|
||||
};
|
||||
|
||||
expect(isAbleToCreateRepos(state)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false, if create link is unavailable", () => {
|
||||
const state = {
|
||||
repos: {
|
||||
list: {
|
||||
_links: {}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
expect(isAbleToCreateRepos(state)).toBe(false);
|
||||
});
|
||||
});
|
||||
878
scm-ui/ui-webapp/src/repos/modules/repos.test.ts
Normal file
878
scm-ui/ui-webapp/src/repos/modules/repos.test.ts
Normal file
@@ -0,0 +1,878 @@
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import reducer, {
|
||||
FETCH_REPOS_PENDING,
|
||||
FETCH_REPOS_SUCCESS,
|
||||
fetchRepos,
|
||||
FETCH_REPOS_FAILURE,
|
||||
fetchReposSuccess,
|
||||
getRepositoryCollection,
|
||||
FETCH_REPOS,
|
||||
isFetchReposPending,
|
||||
getFetchReposFailure,
|
||||
fetchReposByLink,
|
||||
fetchReposByPage,
|
||||
FETCH_REPO,
|
||||
fetchRepoByLink,
|
||||
fetchRepoByName,
|
||||
FETCH_REPO_PENDING,
|
||||
FETCH_REPO_SUCCESS,
|
||||
FETCH_REPO_FAILURE,
|
||||
fetchRepoSuccess,
|
||||
getRepository,
|
||||
isFetchRepoPending,
|
||||
getFetchRepoFailure,
|
||||
CREATE_REPO_PENDING,
|
||||
CREATE_REPO_SUCCESS,
|
||||
createRepo,
|
||||
CREATE_REPO_FAILURE,
|
||||
isCreateRepoPending,
|
||||
CREATE_REPO,
|
||||
getCreateRepoFailure,
|
||||
isAbleToCreateRepos,
|
||||
DELETE_REPO,
|
||||
DELETE_REPO_SUCCESS,
|
||||
deleteRepo,
|
||||
DELETE_REPO_PENDING,
|
||||
DELETE_REPO_FAILURE,
|
||||
isDeleteRepoPending,
|
||||
getDeleteRepoFailure,
|
||||
modifyRepo,
|
||||
MODIFY_REPO_PENDING,
|
||||
MODIFY_REPO_SUCCESS,
|
||||
MODIFY_REPO_FAILURE,
|
||||
MODIFY_REPO,
|
||||
isModifyRepoPending,
|
||||
getModifyRepoFailure,
|
||||
getPermissionsLink,
|
||||
} from './repos';
|
||||
import { Repository, RepositoryCollection } from '@scm-manager/ui-types';
|
||||
|
||||
const hitchhikerPuzzle42: Repository = {
|
||||
contact: 'fourtytwo@hitchhiker.com',
|
||||
creationDate: '2018-07-31T08:58:45.961Z',
|
||||
description: 'the answer to life the universe and everything',
|
||||
namespace: 'hitchhiker',
|
||||
name: 'puzzle42',
|
||||
type: 'svn',
|
||||
_links: {
|
||||
self: {
|
||||
href: 'http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42',
|
||||
},
|
||||
delete: {
|
||||
href: 'http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42',
|
||||
},
|
||||
update: {
|
||||
href: 'http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42',
|
||||
},
|
||||
permissions: {
|
||||
href:
|
||||
'http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42/permissions/',
|
||||
},
|
||||
tags: {
|
||||
href:
|
||||
'http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42/tags/',
|
||||
},
|
||||
branches: {
|
||||
href:
|
||||
'http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42/branches/',
|
||||
},
|
||||
changesets: {
|
||||
href:
|
||||
'http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42/changesets/',
|
||||
},
|
||||
sources: {
|
||||
href:
|
||||
'http://localhost:8081/api/v2/repositories/hitchhiker/puzzle42/sources/',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const hitchhikerRestatend: Repository = {
|
||||
contact: 'restatend@hitchhiker.com',
|
||||
creationDate: '2018-07-31T08:58:32.803Z',
|
||||
description: 'restaurant at the end of the universe',
|
||||
namespace: 'hitchhiker',
|
||||
name: 'restatend',
|
||||
type: 'git',
|
||||
_links: {
|
||||
self: {
|
||||
href: 'http://localhost:8081/api/v2/repositories/hitchhiker/restatend',
|
||||
},
|
||||
delete: {
|
||||
href: 'http://localhost:8081/api/v2/repositories/hitchhiker/restatend',
|
||||
},
|
||||
update: {
|
||||
href: 'http://localhost:8081/api/v2/repositories/hitchhiker/restatend',
|
||||
},
|
||||
permissions: {
|
||||
href:
|
||||
'http://localhost:8081/api/v2/repositories/hitchhiker/restatend/permissions/',
|
||||
},
|
||||
tags: {
|
||||
href:
|
||||
'http://localhost:8081/api/v2/repositories/hitchhiker/restatend/tags/',
|
||||
},
|
||||
branches: {
|
||||
href:
|
||||
'http://localhost:8081/api/v2/repositories/hitchhiker/restatend/branches/',
|
||||
},
|
||||
changesets: {
|
||||
href:
|
||||
'http://localhost:8081/api/v2/repositories/hitchhiker/restatend/changesets/',
|
||||
},
|
||||
sources: {
|
||||
href:
|
||||
'http://localhost:8081/api/v2/repositories/hitchhiker/restatend/sources/',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const slartiFjords: Repository = {
|
||||
contact: 'slartibartfast@hitchhiker.com',
|
||||
description: 'My award-winning fjords from the Norwegian coast',
|
||||
namespace: 'slarti',
|
||||
name: 'fjords',
|
||||
type: 'hg',
|
||||
creationDate: '2018-07-31T08:59:05.653Z',
|
||||
_links: {
|
||||
self: {
|
||||
href: 'http://localhost:8081/api/v2/repositories/slarti/fjords',
|
||||
},
|
||||
delete: {
|
||||
href: 'http://localhost:8081/api/v2/repositories/slarti/fjords',
|
||||
},
|
||||
update: {
|
||||
href: 'http://localhost:8081/api/v2/repositories/slarti/fjords',
|
||||
},
|
||||
permissions: {
|
||||
href:
|
||||
'http://localhost:8081/api/v2/repositories/slarti/fjords/permissions/',
|
||||
},
|
||||
tags: {
|
||||
href: 'http://localhost:8081/api/v2/repositories/slarti/fjords/tags/',
|
||||
},
|
||||
branches: {
|
||||
href: 'http://localhost:8081/api/v2/repositories/slarti/fjords/branches/',
|
||||
},
|
||||
changesets: {
|
||||
href:
|
||||
'http://localhost:8081/api/v2/repositories/slarti/fjords/changesets/',
|
||||
},
|
||||
sources: {
|
||||
href: 'http://localhost:8081/api/v2/repositories/slarti/fjords/sources/',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const repositoryCollection: RepositoryCollection = {
|
||||
page: 0,
|
||||
pageTotal: 1,
|
||||
_links: {
|
||||
self: {
|
||||
href: 'http://localhost:8081/api/v2/repositories/?page=0&pageSize=10',
|
||||
},
|
||||
first: {
|
||||
href: 'http://localhost:8081/api/v2/repositories/?page=0&pageSize=10',
|
||||
},
|
||||
last: {
|
||||
href: 'http://localhost:8081/api/v2/repositories/?page=0&pageSize=10',
|
||||
},
|
||||
create: {
|
||||
href: 'http://localhost:8081/api/v2/repositories/',
|
||||
},
|
||||
},
|
||||
_embedded: {
|
||||
repositories: [hitchhikerPuzzle42, hitchhikerRestatend, slartiFjords],
|
||||
},
|
||||
};
|
||||
|
||||
const repositoryCollectionWithNames: RepositoryCollection = {
|
||||
page: 0,
|
||||
pageTotal: 1,
|
||||
_links: {
|
||||
self: {
|
||||
href: 'http://localhost:8081/api/v2/repositories/?page=0&pageSize=10',
|
||||
},
|
||||
first: {
|
||||
href: 'http://localhost:8081/api/v2/repositories/?page=0&pageSize=10',
|
||||
},
|
||||
last: {
|
||||
href: 'http://localhost:8081/api/v2/repositories/?page=0&pageSize=10',
|
||||
},
|
||||
create: {
|
||||
href: 'http://localhost:8081/api/v2/repositories/',
|
||||
},
|
||||
},
|
||||
_embedded: {
|
||||
repositories: [
|
||||
'hitchhiker/puzzle42',
|
||||
'hitchhiker/restatend',
|
||||
'slarti/fjords',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
describe('repos fetch', () => {
|
||||
const URL = 'repositories';
|
||||
const REPOS_URL = '/api/v2/repositories';
|
||||
const SORT = 'sortBy=namespaceAndName';
|
||||
const REPOS_URL_WITH_SORT = REPOS_URL + '?' + SORT;
|
||||
const mockStore = configureMockStore([thunk]);
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it('should successfully fetch repos', () => {
|
||||
fetchMock.getOnce(REPOS_URL_WITH_SORT, repositoryCollection);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_REPOS_PENDING,
|
||||
},
|
||||
{
|
||||
type: FETCH_REPOS_SUCCESS,
|
||||
payload: repositoryCollection,
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchRepos(URL)).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully fetch page 42', () => {
|
||||
const url = REPOS_URL + '?page=42&' + SORT;
|
||||
fetchMock.getOnce(url, repositoryCollection);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_REPOS_PENDING,
|
||||
},
|
||||
{
|
||||
type: FETCH_REPOS_SUCCESS,
|
||||
payload: repositoryCollection,
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store.dispatch(fetchReposByPage(URL, 43)).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully fetch repos from link', () => {
|
||||
fetchMock.getOnce(
|
||||
REPOS_URL + '?' + SORT + '&page=42',
|
||||
repositoryCollection,
|
||||
);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_REPOS_PENDING,
|
||||
},
|
||||
{
|
||||
type: FETCH_REPOS_SUCCESS,
|
||||
payload: repositoryCollection,
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(
|
||||
fetchReposByLink('/repositories?sortBy=namespaceAndName&page=42'),
|
||||
)
|
||||
.then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should append sortby parameter and successfully fetch repos from link', () => {
|
||||
fetchMock.getOnce(
|
||||
'/api/v2/repositories?one=1&sortBy=namespaceAndName',
|
||||
repositoryCollection,
|
||||
);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_REPOS_PENDING,
|
||||
},
|
||||
{
|
||||
type: FETCH_REPOS_SUCCESS,
|
||||
payload: repositoryCollection,
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store.dispatch(fetchReposByLink('/repositories?one=1')).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch FETCH_REPOS_FAILURE, it the request fails', () => {
|
||||
fetchMock.getOnce(REPOS_URL_WITH_SORT, {
|
||||
status: 500,
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchRepos(URL)).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(FETCH_REPOS_PENDING);
|
||||
expect(actions[1].type).toEqual(FETCH_REPOS_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully fetch repo slarti/fjords by name', () => {
|
||||
fetchMock.getOnce(REPOS_URL + '/slarti/fjords', slartiFjords);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_REPO_PENDING,
|
||||
payload: {
|
||||
namespace: 'slarti',
|
||||
name: 'fjords',
|
||||
},
|
||||
itemId: 'slarti/fjords',
|
||||
},
|
||||
{
|
||||
type: FETCH_REPO_SUCCESS,
|
||||
payload: slartiFjords,
|
||||
itemId: 'slarti/fjords',
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchRepoByName(URL, 'slarti', 'fjords')).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch FETCH_REPO_FAILURE, if the request for slarti/fjords by name fails', () => {
|
||||
fetchMock.getOnce(REPOS_URL + '/slarti/fjords', {
|
||||
status: 500,
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchRepoByName(URL, 'slarti', 'fjords')).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(FETCH_REPO_PENDING);
|
||||
expect(actions[1].type).toEqual(FETCH_REPO_FAILURE);
|
||||
expect(actions[1].payload.namespace).toBe('slarti');
|
||||
expect(actions[1].payload.name).toBe('fjords');
|
||||
expect(actions[1].payload.error).toBeDefined();
|
||||
expect(actions[1].itemId).toBe('slarti/fjords');
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully fetch repo slarti/fjords', () => {
|
||||
fetchMock.getOnce(
|
||||
'http://localhost:8081/api/v2/repositories/slarti/fjords',
|
||||
slartiFjords,
|
||||
);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_REPO_PENDING,
|
||||
payload: {
|
||||
namespace: 'slarti',
|
||||
name: 'fjords',
|
||||
},
|
||||
itemId: 'slarti/fjords',
|
||||
},
|
||||
{
|
||||
type: FETCH_REPO_SUCCESS,
|
||||
payload: slartiFjords,
|
||||
itemId: 'slarti/fjords',
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchRepoByLink(slartiFjords)).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch FETCH_REPO_FAILURE, it the request for slarti/fjords fails', () => {
|
||||
fetchMock.getOnce(
|
||||
'http://localhost:8081/api/v2/repositories/slarti/fjords',
|
||||
{
|
||||
status: 500,
|
||||
},
|
||||
);
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchRepoByLink(slartiFjords)).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(FETCH_REPO_PENDING);
|
||||
expect(actions[1].type).toEqual(FETCH_REPO_FAILURE);
|
||||
expect(actions[1].payload.namespace).toBe('slarti');
|
||||
expect(actions[1].payload.name).toBe('fjords');
|
||||
expect(actions[1].payload.error).toBeDefined();
|
||||
expect(actions[1].itemId).toBe('slarti/fjords');
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully create repo slarti/fjords', () => {
|
||||
fetchMock.postOnce(REPOS_URL, {
|
||||
status: 201,
|
||||
headers: {
|
||||
location: 'repositories/slarti/fjords',
|
||||
},
|
||||
});
|
||||
|
||||
fetchMock.getOnce(REPOS_URL + '/slarti/fjords', slartiFjords);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: CREATE_REPO_PENDING,
|
||||
},
|
||||
{
|
||||
type: CREATE_REPO_SUCCESS,
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(createRepo(URL, slartiFjords)).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully create repo slarti/fjords and call the callback', () => {
|
||||
fetchMock.postOnce(REPOS_URL, {
|
||||
status: 201,
|
||||
headers: {
|
||||
location: 'repositories/slarti/fjords',
|
||||
},
|
||||
});
|
||||
|
||||
fetchMock.getOnce(REPOS_URL + '/slarti/fjords', slartiFjords);
|
||||
|
||||
let callMe = 'not yet';
|
||||
|
||||
const callback = (r: any) => {
|
||||
expect(r).toEqual(slartiFjords);
|
||||
callMe = 'yeah';
|
||||
};
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(createRepo(URL, slartiFjords, callback)).then(() => {
|
||||
expect(callMe).toBe('yeah');
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch failure if server returns status code 500', () => {
|
||||
fetchMock.postOnce(REPOS_URL, {
|
||||
status: 500,
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(createRepo(URL, slartiFjords)).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(CREATE_REPO_PENDING);
|
||||
expect(actions[1].type).toEqual(CREATE_REPO_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully delete repo slarti/fjords', () => {
|
||||
fetchMock.delete(
|
||||
'http://localhost:8081/api/v2/repositories/slarti/fjords',
|
||||
{
|
||||
status: 204,
|
||||
},
|
||||
);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: DELETE_REPO_PENDING,
|
||||
payload: slartiFjords,
|
||||
itemId: 'slarti/fjords',
|
||||
},
|
||||
{
|
||||
type: DELETE_REPO_SUCCESS,
|
||||
payload: slartiFjords,
|
||||
itemId: 'slarti/fjords',
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(deleteRepo(slartiFjords)).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully delete repo slarti/fjords and call the callback', () => {
|
||||
fetchMock.delete(
|
||||
'http://localhost:8081/api/v2/repositories/slarti/fjords',
|
||||
{
|
||||
status: 204,
|
||||
},
|
||||
);
|
||||
|
||||
let callMe = 'not yet';
|
||||
|
||||
const callback = () => {
|
||||
callMe = 'yeah';
|
||||
};
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(deleteRepo(slartiFjords, callback)).then(() => {
|
||||
expect(callMe).toBe('yeah');
|
||||
});
|
||||
});
|
||||
|
||||
it('should disapatch failure on delete, if server returns status code 500', () => {
|
||||
fetchMock.delete(
|
||||
'http://localhost:8081/api/v2/repositories/slarti/fjords',
|
||||
{
|
||||
status: 500,
|
||||
},
|
||||
);
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(deleteRepo(slartiFjords)).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(DELETE_REPO_PENDING);
|
||||
expect(actions[1].type).toEqual(DELETE_REPO_FAILURE);
|
||||
expect(actions[1].payload.repository).toBe(slartiFjords);
|
||||
expect(actions[1].payload.error).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully modify slarti/fjords repo', () => {
|
||||
fetchMock.putOnce(slartiFjords._links.update.href, {
|
||||
status: 204,
|
||||
});
|
||||
fetchMock.getOnce(
|
||||
'http://localhost:8081/api/v2/repositories/slarti/fjords',
|
||||
{
|
||||
status: 500,
|
||||
},
|
||||
);
|
||||
|
||||
let editedFjords = {
|
||||
...slartiFjords,
|
||||
};
|
||||
editedFjords.description = 'coast of africa';
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store.dispatch(modifyRepo(editedFjords)).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(MODIFY_REPO_PENDING);
|
||||
expect(actions[1].type).toEqual(MODIFY_REPO_SUCCESS);
|
||||
expect(actions[2].type).toEqual(FETCH_REPO_PENDING);
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully modify slarti/fjords repo and call the callback', () => {
|
||||
fetchMock.putOnce(slartiFjords._links.update.href, {
|
||||
status: 204,
|
||||
});
|
||||
fetchMock.getOnce(
|
||||
'http://localhost:8081/api/v2/repositories/slarti/fjords',
|
||||
{
|
||||
status: 500,
|
||||
},
|
||||
);
|
||||
|
||||
let editedFjords = {
|
||||
...slartiFjords,
|
||||
};
|
||||
editedFjords.description = 'coast of africa';
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
let called = false;
|
||||
const callback = () => {
|
||||
called = true;
|
||||
};
|
||||
|
||||
return store.dispatch(modifyRepo(editedFjords, callback)).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(MODIFY_REPO_PENDING);
|
||||
expect(actions[1].type).toEqual(MODIFY_REPO_SUCCESS);
|
||||
expect(actions[2].type).toEqual(FETCH_REPO_PENDING);
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail modifying on HTTP 500', () => {
|
||||
fetchMock.putOnce(slartiFjords._links.update.href, {
|
||||
status: 500,
|
||||
});
|
||||
|
||||
let editedFjords = {
|
||||
...slartiFjords,
|
||||
};
|
||||
editedFjords.description = 'coast of africa';
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store.dispatch(modifyRepo(editedFjords)).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(MODIFY_REPO_PENDING);
|
||||
expect(actions[1].type).toEqual(MODIFY_REPO_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('repos 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 repositories by it's namespace and name on FETCH_REPOS_SUCCESS", () => {
|
||||
const newState = reducer({}, fetchReposSuccess(repositoryCollection));
|
||||
expect(newState.list.page).toBe(0);
|
||||
expect(newState.list.pageTotal).toBe(1);
|
||||
expect(newState.list._embedded.repositories).toEqual([
|
||||
'hitchhiker/puzzle42',
|
||||
'hitchhiker/restatend',
|
||||
'slarti/fjords',
|
||||
]);
|
||||
|
||||
expect(newState.byNames['hitchhiker/puzzle42']).toBe(hitchhikerPuzzle42);
|
||||
expect(newState.byNames['hitchhiker/restatend']).toBe(hitchhikerRestatend);
|
||||
expect(newState.byNames['slarti/fjords']).toBe(slartiFjords);
|
||||
});
|
||||
|
||||
it('should store the repo at byNames', () => {
|
||||
const newState = reducer({}, fetchRepoSuccess(slartiFjords));
|
||||
expect(newState.byNames['slarti/fjords']).toBe(slartiFjords);
|
||||
});
|
||||
});
|
||||
|
||||
describe('repos selectors', () => {
|
||||
const error = new Error('something goes wrong');
|
||||
|
||||
it('should return the repositories collection', () => {
|
||||
const state = {
|
||||
repos: {
|
||||
list: repositoryCollectionWithNames,
|
||||
byNames: {
|
||||
'hitchhiker/puzzle42': hitchhikerPuzzle42,
|
||||
'hitchhiker/restatend': hitchhikerRestatend,
|
||||
'slarti/fjords': slartiFjords,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const collection = getRepositoryCollection(state);
|
||||
expect(collection).toEqual(repositoryCollection);
|
||||
});
|
||||
|
||||
it('should return true, when fetch repos is pending', () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[FETCH_REPOS]: true,
|
||||
},
|
||||
};
|
||||
expect(isFetchReposPending(state)).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false, when fetch repos is not pending', () => {
|
||||
expect(isFetchReposPending({})).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return error when fetch repos did fail', () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[FETCH_REPOS]: error,
|
||||
},
|
||||
};
|
||||
expect(getFetchReposFailure(state)).toEqual(error);
|
||||
});
|
||||
|
||||
it('should return undefined when fetch repos did not fail', () => {
|
||||
expect(getFetchReposFailure({})).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should return the repository collection', () => {
|
||||
const state = {
|
||||
repos: {
|
||||
byNames: {
|
||||
'slarti/fjords': slartiFjords,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const repository = getRepository(state, 'slarti', 'fjords');
|
||||
expect(repository).toEqual(slartiFjords);
|
||||
});
|
||||
|
||||
it('should return permissions link', () => {
|
||||
const state = {
|
||||
repos: {
|
||||
byNames: {
|
||||
'slarti/fjords': slartiFjords,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const link = getPermissionsLink(state, 'slarti', 'fjords');
|
||||
expect(link).toEqual(
|
||||
'http://localhost:8081/api/v2/repositories/slarti/fjords/permissions/',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return true, when fetch repo is pending', () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[FETCH_REPO + '/slarti/fjords']: true,
|
||||
},
|
||||
};
|
||||
expect(isFetchRepoPending(state, 'slarti', 'fjords')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false, when fetch repo is not pending', () => {
|
||||
expect(isFetchRepoPending({}, 'slarti', 'fjords')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return error when fetch repo did fail', () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[FETCH_REPO + '/slarti/fjords']: error,
|
||||
},
|
||||
};
|
||||
expect(getFetchRepoFailure(state, 'slarti', 'fjords')).toEqual(error);
|
||||
});
|
||||
|
||||
it('should return undefined when fetch repo did not fail', () => {
|
||||
expect(getFetchRepoFailure({}, 'slarti', 'fjords')).toBe(undefined);
|
||||
});
|
||||
|
||||
// create
|
||||
|
||||
it('should return true, when create repo is pending', () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[CREATE_REPO]: true,
|
||||
},
|
||||
};
|
||||
expect(isCreateRepoPending(state)).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false, when create repo is not pending', () => {
|
||||
expect(isCreateRepoPending({})).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return error when create repo did fail', () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[CREATE_REPO]: error,
|
||||
},
|
||||
};
|
||||
expect(getCreateRepoFailure(state)).toEqual(error);
|
||||
});
|
||||
|
||||
it('should return undefined when create repo did not fail', () => {
|
||||
expect(getCreateRepoFailure({})).toBe(undefined);
|
||||
});
|
||||
|
||||
// modify
|
||||
|
||||
it('should return true, when modify repo is pending', () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[MODIFY_REPO + '/slarti/fjords']: true,
|
||||
},
|
||||
};
|
||||
|
||||
expect(isModifyRepoPending(state, 'slarti', 'fjords')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false, when modify repo is not pending', () => {
|
||||
expect(isModifyRepoPending({}, 'slarti', 'fjords')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return error, when modify repo failed', () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[MODIFY_REPO + '/slarti/fjords']: error,
|
||||
},
|
||||
};
|
||||
|
||||
expect(getModifyRepoFailure(state, 'slarti', 'fjords')).toEqual(error);
|
||||
});
|
||||
|
||||
it('should return undefined, when modify did not fail', () => {
|
||||
expect(getModifyRepoFailure({}, 'slarti', 'fjords')).toBeUndefined();
|
||||
});
|
||||
|
||||
// delete
|
||||
|
||||
it('should return true, when delete repo is pending', () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[DELETE_REPO + '/slarti/fjords']: true,
|
||||
},
|
||||
};
|
||||
expect(isDeleteRepoPending(state, 'slarti', 'fjords')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false, when delete repo is not pending', () => {
|
||||
expect(isDeleteRepoPending({}, 'slarti', 'fjords')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return error when delete repo did fail', () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[DELETE_REPO + '/slarti/fjords']: error,
|
||||
},
|
||||
};
|
||||
expect(getDeleteRepoFailure(state, 'slarti', 'fjords')).toEqual(error);
|
||||
});
|
||||
|
||||
it('should return undefined when delete repo did not fail', () => {
|
||||
expect(getDeleteRepoFailure({}, 'slarti', 'fjords')).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should return true if the list contains the create link', () => {
|
||||
const state = {
|
||||
repos: {
|
||||
list: repositoryCollection,
|
||||
},
|
||||
};
|
||||
|
||||
expect(isAbleToCreateRepos(state)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false, if create link is unavailable', () => {
|
||||
const state = {
|
||||
repos: {
|
||||
list: {
|
||||
_links: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(isAbleToCreateRepos(state)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,46 +1,45 @@
|
||||
// @flow
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
import * as types from "../../modules/types";
|
||||
import type {
|
||||
import { apiClient } from '@scm-manager/ui-components';
|
||||
import * as types from '../../modules/types';
|
||||
import {
|
||||
Action,
|
||||
Repository,
|
||||
RepositoryCollection
|
||||
} from "@scm-manager/ui-types";
|
||||
import { isPending } from "../../modules/pending";
|
||||
import { getFailure } from "../../modules/failure";
|
||||
RepositoryCollection,
|
||||
} from '@scm-manager/ui-types';
|
||||
import { isPending } from '../../modules/pending';
|
||||
import { getFailure } from '../../modules/failure';
|
||||
|
||||
export const FETCH_REPOS = "scm/repos/FETCH_REPOS";
|
||||
export const FETCH_REPOS = 'scm/repos/FETCH_REPOS';
|
||||
export const FETCH_REPOS_PENDING = `${FETCH_REPOS}_${types.PENDING_SUFFIX}`;
|
||||
export const FETCH_REPOS_SUCCESS = `${FETCH_REPOS}_${types.SUCCESS_SUFFIX}`;
|
||||
export const FETCH_REPOS_FAILURE = `${FETCH_REPOS}_${types.FAILURE_SUFFIX}`;
|
||||
|
||||
export const FETCH_REPO = "scm/repos/FETCH_REPO";
|
||||
export const FETCH_REPO = 'scm/repos/FETCH_REPO';
|
||||
export const FETCH_REPO_PENDING = `${FETCH_REPO}_${types.PENDING_SUFFIX}`;
|
||||
export const FETCH_REPO_SUCCESS = `${FETCH_REPO}_${types.SUCCESS_SUFFIX}`;
|
||||
export const FETCH_REPO_FAILURE = `${FETCH_REPO}_${types.FAILURE_SUFFIX}`;
|
||||
|
||||
export const CREATE_REPO = "scm/repos/CREATE_REPO";
|
||||
export const CREATE_REPO = 'scm/repos/CREATE_REPO';
|
||||
export const CREATE_REPO_PENDING = `${CREATE_REPO}_${types.PENDING_SUFFIX}`;
|
||||
export const CREATE_REPO_SUCCESS = `${CREATE_REPO}_${types.SUCCESS_SUFFIX}`;
|
||||
export const CREATE_REPO_FAILURE = `${CREATE_REPO}_${types.FAILURE_SUFFIX}`;
|
||||
export const CREATE_REPO_RESET = `${CREATE_REPO}_${types.RESET_SUFFIX}`;
|
||||
|
||||
export const MODIFY_REPO = "scm/repos/MODIFY_REPO";
|
||||
export const MODIFY_REPO = 'scm/repos/MODIFY_REPO';
|
||||
export const MODIFY_REPO_PENDING = `${MODIFY_REPO}_${types.PENDING_SUFFIX}`;
|
||||
export const MODIFY_REPO_SUCCESS = `${MODIFY_REPO}_${types.SUCCESS_SUFFIX}`;
|
||||
export const MODIFY_REPO_FAILURE = `${MODIFY_REPO}_${types.FAILURE_SUFFIX}`;
|
||||
export const MODIFY_REPO_RESET = `${MODIFY_REPO}_${types.RESET_SUFFIX}`;
|
||||
|
||||
export const DELETE_REPO = "scm/repos/DELETE_REPO";
|
||||
export const DELETE_REPO = 'scm/repos/DELETE_REPO';
|
||||
export const DELETE_REPO_PENDING = `${DELETE_REPO}_${types.PENDING_SUFFIX}`;
|
||||
export const DELETE_REPO_SUCCESS = `${DELETE_REPO}_${types.SUCCESS_SUFFIX}`;
|
||||
export const DELETE_REPO_FAILURE = `${DELETE_REPO}_${types.FAILURE_SUFFIX}`;
|
||||
|
||||
const CONTENT_TYPE = "application/vnd.scmm-repository+json;v=2";
|
||||
const CONTENT_TYPE = 'application/vnd.scmm-repository+json;v=2';
|
||||
|
||||
// fetch repos
|
||||
|
||||
const SORT_BY = "sortBy=namespaceAndName";
|
||||
const SORT_BY = 'sortBy=namespaceAndName';
|
||||
|
||||
export function fetchRepos(link: string) {
|
||||
return fetchReposByLink(link);
|
||||
@@ -49,7 +48,7 @@ export function fetchRepos(link: string) {
|
||||
export function fetchReposByPage(link: string, page: number, filter?: string) {
|
||||
if (filter) {
|
||||
return fetchReposByLink(
|
||||
`${link}?page=${page - 1}&q=${decodeURIComponent(filter)}`
|
||||
`${link}?page=${page - 1}&q=${decodeURIComponent(filter)}`,
|
||||
);
|
||||
}
|
||||
return fetchReposByLink(`${link}?page=${page - 1}`);
|
||||
@@ -60,10 +59,10 @@ function appendSortByLink(url: string) {
|
||||
return url;
|
||||
}
|
||||
let urlWithSortBy = url;
|
||||
if (url.includes("?")) {
|
||||
urlWithSortBy += "&";
|
||||
if (url.includes('?')) {
|
||||
urlWithSortBy += '&';
|
||||
} else {
|
||||
urlWithSortBy += "?";
|
||||
urlWithSortBy += '?';
|
||||
}
|
||||
return urlWithSortBy + SORT_BY;
|
||||
}
|
||||
@@ -86,21 +85,21 @@ export function fetchReposByLink(link: string) {
|
||||
|
||||
export function fetchReposPending(): Action {
|
||||
return {
|
||||
type: FETCH_REPOS_PENDING
|
||||
type: FETCH_REPOS_PENDING,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchReposSuccess(repositories: RepositoryCollection): Action {
|
||||
return {
|
||||
type: FETCH_REPOS_SUCCESS,
|
||||
payload: repositories
|
||||
payload: repositories,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchReposFailure(err: Error): Action {
|
||||
return {
|
||||
type: FETCH_REPOS_FAILURE,
|
||||
payload: err
|
||||
payload: err,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -110,7 +109,7 @@ export function fetchRepoByLink(repo: Repository) {
|
||||
}
|
||||
|
||||
export function fetchRepoByName(link: string, namespace: string, name: string) {
|
||||
const repoUrl = link.endsWith("/") ? link : link + "/";
|
||||
const repoUrl = link.endsWith('/') ? link : link + '/';
|
||||
return fetchRepo(`${repoUrl}${namespace}/${name}`, namespace, name);
|
||||
}
|
||||
|
||||
@@ -134,9 +133,9 @@ export function fetchRepoPending(namespace: string, name: string): Action {
|
||||
type: FETCH_REPO_PENDING,
|
||||
payload: {
|
||||
namespace,
|
||||
name
|
||||
name,
|
||||
},
|
||||
itemId: namespace + "/" + name
|
||||
itemId: namespace + '/' + name,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -144,23 +143,23 @@ export function fetchRepoSuccess(repository: Repository): Action {
|
||||
return {
|
||||
type: FETCH_REPO_SUCCESS,
|
||||
payload: repository,
|
||||
itemId: createIdentifier(repository)
|
||||
itemId: createIdentifier(repository),
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchRepoFailure(
|
||||
namespace: string,
|
||||
name: string,
|
||||
error: Error
|
||||
error: Error,
|
||||
): Action {
|
||||
return {
|
||||
type: FETCH_REPO_FAILURE,
|
||||
payload: {
|
||||
namespace,
|
||||
name,
|
||||
error
|
||||
error,
|
||||
},
|
||||
itemId: namespace + "/" + name
|
||||
itemId: namespace + '/' + name,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -169,14 +168,14 @@ export function fetchRepoFailure(
|
||||
export function createRepo(
|
||||
link: string,
|
||||
repository: Repository,
|
||||
callback?: (repo: Repository) => void
|
||||
callback?: (repo: Repository) => void,
|
||||
) {
|
||||
return function(dispatch: any) {
|
||||
dispatch(createRepoPending());
|
||||
return apiClient
|
||||
.post(link, repository, CONTENT_TYPE)
|
||||
.then(response => {
|
||||
const location = response.headers.get("Location");
|
||||
const location = response.headers.get('Location');
|
||||
dispatch(createRepoSuccess());
|
||||
return apiClient.get(location);
|
||||
})
|
||||
@@ -194,26 +193,26 @@ export function createRepo(
|
||||
|
||||
export function createRepoPending(): Action {
|
||||
return {
|
||||
type: CREATE_REPO_PENDING
|
||||
type: CREATE_REPO_PENDING,
|
||||
};
|
||||
}
|
||||
|
||||
export function createRepoSuccess(): Action {
|
||||
return {
|
||||
type: CREATE_REPO_SUCCESS
|
||||
type: CREATE_REPO_SUCCESS,
|
||||
};
|
||||
}
|
||||
|
||||
export function createRepoFailure(err: Error): Action {
|
||||
return {
|
||||
type: CREATE_REPO_FAILURE,
|
||||
payload: err
|
||||
payload: err,
|
||||
};
|
||||
}
|
||||
|
||||
export function createRepoReset(): Action {
|
||||
return {
|
||||
type: CREATE_REPO_RESET
|
||||
type: CREATE_REPO_RESET,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -244,7 +243,7 @@ export function modifyRepoPending(repository: Repository): Action {
|
||||
return {
|
||||
type: MODIFY_REPO_PENDING,
|
||||
payload: repository,
|
||||
itemId: createIdentifier(repository)
|
||||
itemId: createIdentifier(repository),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -252,26 +251,31 @@ export function modifyRepoSuccess(repository: Repository): Action {
|
||||
return {
|
||||
type: MODIFY_REPO_SUCCESS,
|
||||
payload: repository,
|
||||
itemId: createIdentifier(repository)
|
||||
itemId: createIdentifier(repository),
|
||||
};
|
||||
}
|
||||
|
||||
export function modifyRepoFailure(
|
||||
repository: Repository,
|
||||
error: Error
|
||||
error: Error,
|
||||
): Action {
|
||||
return {
|
||||
type: MODIFY_REPO_FAILURE,
|
||||
payload: { error, repository },
|
||||
itemId: createIdentifier(repository)
|
||||
payload: {
|
||||
error,
|
||||
repository,
|
||||
},
|
||||
itemId: createIdentifier(repository),
|
||||
};
|
||||
}
|
||||
|
||||
export function modifyRepoReset(repository: Repository): Action {
|
||||
return {
|
||||
type: MODIFY_REPO_RESET,
|
||||
payload: { repository },
|
||||
itemId: createIdentifier(repository)
|
||||
payload: {
|
||||
repository,
|
||||
},
|
||||
itemId: createIdentifier(repository),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -298,7 +302,7 @@ export function deleteRepoPending(repository: Repository): Action {
|
||||
return {
|
||||
type: DELETE_REPO_PENDING,
|
||||
payload: repository,
|
||||
itemId: createIdentifier(repository)
|
||||
itemId: createIdentifier(repository),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -306,32 +310,32 @@ export function deleteRepoSuccess(repository: Repository): Action {
|
||||
return {
|
||||
type: DELETE_REPO_SUCCESS,
|
||||
payload: repository,
|
||||
itemId: createIdentifier(repository)
|
||||
itemId: createIdentifier(repository),
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteRepoFailure(
|
||||
repository: Repository,
|
||||
error: Error
|
||||
error: Error,
|
||||
): Action {
|
||||
return {
|
||||
type: DELETE_REPO_FAILURE,
|
||||
payload: {
|
||||
error,
|
||||
repository
|
||||
repository,
|
||||
},
|
||||
itemId: createIdentifier(repository)
|
||||
itemId: createIdentifier(repository),
|
||||
};
|
||||
}
|
||||
|
||||
// reducer
|
||||
|
||||
function createIdentifier(repository: Repository) {
|
||||
return repository.namespace + "/" + repository.name;
|
||||
return repository.namespace + '/' + repository.name;
|
||||
}
|
||||
|
||||
function normalizeByNamespaceAndName(
|
||||
repositoryCollection: RepositoryCollection
|
||||
repositoryCollection: RepositoryCollection,
|
||||
) {
|
||||
const names = [];
|
||||
const byNames = {};
|
||||
@@ -344,28 +348,30 @@ function normalizeByNamespaceAndName(
|
||||
list: {
|
||||
...repositoryCollection,
|
||||
_embedded: {
|
||||
repositories: names
|
||||
}
|
||||
repositories: names,
|
||||
},
|
||||
},
|
||||
byNames: byNames
|
||||
byNames: byNames,
|
||||
};
|
||||
}
|
||||
|
||||
const reducerByNames = (state: Object, repository: Repository) => {
|
||||
const reducerByNames = (state: object, repository: Repository) => {
|
||||
const identifier = createIdentifier(repository);
|
||||
return {
|
||||
...state,
|
||||
byNames: {
|
||||
...state.byNames,
|
||||
[identifier]: repository
|
||||
}
|
||||
[identifier]: repository,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default function reducer(
|
||||
state: Object = {},
|
||||
action: Action = { type: "UNKNOWN" }
|
||||
): Object {
|
||||
state: object = {},
|
||||
action: Action = {
|
||||
type: 'UNKNOWN',
|
||||
},
|
||||
): object {
|
||||
if (!action.payload) {
|
||||
return state;
|
||||
}
|
||||
@@ -382,7 +388,7 @@ export default function reducer(
|
||||
|
||||
// selectors
|
||||
|
||||
export function getRepositoryCollection(state: Object) {
|
||||
export function getRepositoryCollection(state: object) {
|
||||
if (state.repos && state.repos.list && state.repos.byNames) {
|
||||
const repositories = [];
|
||||
for (let repositoryName of state.repos.list._embedded.repositories) {
|
||||
@@ -391,43 +397,43 @@ export function getRepositoryCollection(state: Object) {
|
||||
return {
|
||||
...state.repos.list,
|
||||
_embedded: {
|
||||
repositories
|
||||
}
|
||||
repositories,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function isFetchReposPending(state: Object) {
|
||||
export function isFetchReposPending(state: object) {
|
||||
return isPending(state, FETCH_REPOS);
|
||||
}
|
||||
|
||||
export function getFetchReposFailure(state: Object) {
|
||||
export function getFetchReposFailure(state: object) {
|
||||
return getFailure(state, FETCH_REPOS);
|
||||
}
|
||||
|
||||
export function getRepository(state: Object, namespace: string, name: string) {
|
||||
export function getRepository(state: object, namespace: string, name: string) {
|
||||
if (state.repos && state.repos.byNames) {
|
||||
return state.repos.byNames[namespace + "/" + name];
|
||||
return state.repos.byNames[namespace + '/' + name];
|
||||
}
|
||||
}
|
||||
|
||||
export function isFetchRepoPending(
|
||||
state: Object,
|
||||
state: object,
|
||||
namespace: string,
|
||||
name: string
|
||||
name: string,
|
||||
) {
|
||||
return isPending(state, FETCH_REPO, namespace + "/" + name);
|
||||
return isPending(state, FETCH_REPO, namespace + '/' + name);
|
||||
}
|
||||
|
||||
export function getFetchRepoFailure(
|
||||
state: Object,
|
||||
state: object,
|
||||
namespace: string,
|
||||
name: string
|
||||
name: string,
|
||||
) {
|
||||
return getFailure(state, FETCH_REPO, namespace + "/" + name);
|
||||
return getFailure(state, FETCH_REPO, namespace + '/' + name);
|
||||
}
|
||||
|
||||
export function isAbleToCreateRepos(state: Object) {
|
||||
export function isAbleToCreateRepos(state: object) {
|
||||
return !!(
|
||||
state.repos &&
|
||||
state.repos.list &&
|
||||
@@ -436,50 +442,50 @@ export function isAbleToCreateRepos(state: Object) {
|
||||
);
|
||||
}
|
||||
|
||||
export function isCreateRepoPending(state: Object) {
|
||||
export function isCreateRepoPending(state: object) {
|
||||
return isPending(state, CREATE_REPO);
|
||||
}
|
||||
|
||||
export function getCreateRepoFailure(state: Object) {
|
||||
export function getCreateRepoFailure(state: object) {
|
||||
return getFailure(state, CREATE_REPO);
|
||||
}
|
||||
|
||||
export function isModifyRepoPending(
|
||||
state: Object,
|
||||
state: object,
|
||||
namespace: string,
|
||||
name: string
|
||||
name: string,
|
||||
) {
|
||||
return isPending(state, MODIFY_REPO, namespace + "/" + name);
|
||||
return isPending(state, MODIFY_REPO, namespace + '/' + name);
|
||||
}
|
||||
|
||||
export function getModifyRepoFailure(
|
||||
state: Object,
|
||||
state: object,
|
||||
namespace: string,
|
||||
name: string
|
||||
name: string,
|
||||
) {
|
||||
return getFailure(state, MODIFY_REPO, namespace + "/" + name);
|
||||
return getFailure(state, MODIFY_REPO, namespace + '/' + name);
|
||||
}
|
||||
|
||||
export function isDeleteRepoPending(
|
||||
state: Object,
|
||||
state: object,
|
||||
namespace: string,
|
||||
name: string
|
||||
name: string,
|
||||
) {
|
||||
return isPending(state, DELETE_REPO, namespace + "/" + name);
|
||||
return isPending(state, DELETE_REPO, namespace + '/' + name);
|
||||
}
|
||||
|
||||
export function getDeleteRepoFailure(
|
||||
state: Object,
|
||||
state: object,
|
||||
namespace: string,
|
||||
name: string
|
||||
name: string,
|
||||
) {
|
||||
return getFailure(state, DELETE_REPO, namespace + "/" + name);
|
||||
return getFailure(state, DELETE_REPO, namespace + '/' + name);
|
||||
}
|
||||
|
||||
export function getPermissionsLink(
|
||||
state: Object,
|
||||
state: object,
|
||||
namespace: string,
|
||||
name: string
|
||||
name: string,
|
||||
) {
|
||||
const repo = getRepository(state, namespace, name);
|
||||
return repo && repo._links ? repo._links.permissions.href : undefined;
|
||||
@@ -1,8 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import fetchMock from "fetch-mock";
|
||||
import configureMockStore from "redux-mock-store";
|
||||
import thunk from "redux-thunk";
|
||||
import fetchMock from 'fetch-mock';
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import {
|
||||
FETCH_REPOSITORY_TYPES,
|
||||
FETCH_REPOSITORY_TYPES_FAILURE,
|
||||
@@ -13,91 +11,91 @@ import {
|
||||
getFetchRepositoryTypesFailure,
|
||||
getRepositoryTypes,
|
||||
isFetchRepositoryTypesPending,
|
||||
shouldFetchRepositoryTypes
|
||||
} from "./repositoryTypes";
|
||||
import reducer from "./repositoryTypes";
|
||||
shouldFetchRepositoryTypes,
|
||||
} from './repositoryTypes';
|
||||
import reducer from './repositoryTypes';
|
||||
|
||||
const git = {
|
||||
name: "git",
|
||||
displayName: "Git",
|
||||
name: 'git',
|
||||
displayName: 'Git',
|
||||
_links: {
|
||||
self: {
|
||||
href: "http://localhost:8081/api/v2/repositoryTypes/git"
|
||||
}
|
||||
}
|
||||
href: 'http://localhost:8081/api/v2/repositoryTypes/git',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const hg = {
|
||||
name: "hg",
|
||||
displayName: "Mercurial",
|
||||
name: 'hg',
|
||||
displayName: 'Mercurial',
|
||||
_links: {
|
||||
self: {
|
||||
href: "http://localhost:8081/api/v2/repositoryTypes/hg"
|
||||
}
|
||||
}
|
||||
href: 'http://localhost:8081/api/v2/repositoryTypes/hg',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const svn = {
|
||||
name: "svn",
|
||||
displayName: "Subversion",
|
||||
name: 'svn',
|
||||
displayName: 'Subversion',
|
||||
_links: {
|
||||
self: {
|
||||
href: "http://localhost:8081/api/v2/repositoryTypes/svn"
|
||||
}
|
||||
}
|
||||
href: 'http://localhost:8081/api/v2/repositoryTypes/svn',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const collection = {
|
||||
_embedded: {
|
||||
repositoryTypes: [git, hg, svn]
|
||||
repositoryTypes: [git, hg, svn],
|
||||
},
|
||||
_links: {
|
||||
self: {
|
||||
href: "http://localhost:8081/api/v2/repositoryTypes"
|
||||
}
|
||||
}
|
||||
href: 'http://localhost:8081/api/v2/repositoryTypes',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe("repository types caching", () => {
|
||||
it("should fetch repository types, on empty state", () => {
|
||||
describe('repository types caching', () => {
|
||||
it('should fetch repository types, on empty state', () => {
|
||||
expect(shouldFetchRepositoryTypes({})).toBe(true);
|
||||
});
|
||||
|
||||
it("should fetch repository types, if the state contains an empty array", () => {
|
||||
it('should fetch repository types, if the state contains an empty array', () => {
|
||||
const state = {
|
||||
repositoryTypes: []
|
||||
repositoryTypes: [],
|
||||
};
|
||||
expect(shouldFetchRepositoryTypes(state)).toBe(true);
|
||||
});
|
||||
|
||||
it("should not fetch repository types, on pending state", () => {
|
||||
it('should not fetch repository types, on pending state', () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[FETCH_REPOSITORY_TYPES]: true
|
||||
}
|
||||
[FETCH_REPOSITORY_TYPES]: true,
|
||||
},
|
||||
};
|
||||
expect(shouldFetchRepositoryTypes(state)).toBe(false);
|
||||
});
|
||||
|
||||
it("should not fetch repository types, on failure state", () => {
|
||||
it('should not fetch repository types, on failure state', () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[FETCH_REPOSITORY_TYPES]: new Error("no...")
|
||||
}
|
||||
[FETCH_REPOSITORY_TYPES]: new Error('no...'),
|
||||
},
|
||||
};
|
||||
expect(shouldFetchRepositoryTypes(state)).toBe(false);
|
||||
});
|
||||
|
||||
it("should not fetch repository types, if they are already fetched", () => {
|
||||
it('should not fetch repository types, if they are already fetched', () => {
|
||||
const state = {
|
||||
repositoryTypes: [git, hg, svn]
|
||||
repositoryTypes: [git, hg, svn],
|
||||
};
|
||||
expect(shouldFetchRepositoryTypes(state)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("repository types fetch", () => {
|
||||
const URL = "/api/v2/repositoryTypes";
|
||||
describe('repository types fetch', () => {
|
||||
const URL = '/api/v2/repositoryTypes';
|
||||
const mockStore = configureMockStore([thunk]);
|
||||
|
||||
afterEach(() => {
|
||||
@@ -105,15 +103,17 @@ describe("repository types fetch", () => {
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it("should successfully fetch repository types", () => {
|
||||
it('should successfully fetch repository types', () => {
|
||||
fetchMock.getOnce(URL, collection);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: FETCH_REPOSITORY_TYPES_PENDING },
|
||||
{
|
||||
type: FETCH_REPOSITORY_TYPES_PENDING,
|
||||
},
|
||||
{
|
||||
type: FETCH_REPOSITORY_TYPES_SUCCESS,
|
||||
payload: collection
|
||||
}
|
||||
payload: collection,
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
@@ -122,9 +122,9 @@ describe("repository types fetch", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch FETCH_REPOSITORY_TYPES_FAILURE on server error", () => {
|
||||
it('should dispatch FETCH_REPOSITORY_TYPES_FAILURE on server error', () => {
|
||||
fetchMock.getOnce(URL, {
|
||||
status: 500
|
||||
status: 500,
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
@@ -136,63 +136,63 @@ describe("repository types fetch", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch not dispatch any action, if the repository types are already fetched", () => {
|
||||
it('should dispatch not dispatch any action, if the repository types are already fetched', () => {
|
||||
const store = mockStore({
|
||||
repositoryTypes: [git, hg, svn]
|
||||
repositoryTypes: [git, hg, svn],
|
||||
});
|
||||
store.dispatch(fetchRepositoryTypesIfNeeded());
|
||||
expect(store.getActions().length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("repository types reducer", () => {
|
||||
it("should return unmodified state on unknown action", () => {
|
||||
describe('repository types reducer', () => {
|
||||
it('should return unmodified state on unknown action', () => {
|
||||
const state = [];
|
||||
expect(reducer(state)).toBe(state);
|
||||
});
|
||||
it("should store the repository types on FETCH_REPOSITORY_TYPES_SUCCESS", () => {
|
||||
it('should store the repository types on FETCH_REPOSITORY_TYPES_SUCCESS', () => {
|
||||
const newState = reducer([], fetchRepositoryTypesSuccess(collection));
|
||||
expect(newState).toEqual([git, hg, svn]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("repository types selectors", () => {
|
||||
const error = new Error("The end of the universe");
|
||||
describe('repository types selectors', () => {
|
||||
const error = new Error('The end of the universe');
|
||||
|
||||
it("should return an emtpy array", () => {
|
||||
it('should return an emtpy array', () => {
|
||||
expect(getRepositoryTypes({})).toEqual([]);
|
||||
});
|
||||
|
||||
it("should return the repository types", () => {
|
||||
it('should return the repository types', () => {
|
||||
const state = {
|
||||
repositoryTypes: [git, hg, svn]
|
||||
repositoryTypes: [git, hg, svn],
|
||||
};
|
||||
expect(getRepositoryTypes(state)).toEqual([git, hg, svn]);
|
||||
});
|
||||
|
||||
it("should return true, when fetch repository types is pending", () => {
|
||||
it('should return true, when fetch repository types is pending', () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[FETCH_REPOSITORY_TYPES]: true
|
||||
}
|
||||
[FETCH_REPOSITORY_TYPES]: true,
|
||||
},
|
||||
};
|
||||
expect(isFetchRepositoryTypesPending(state)).toEqual(true);
|
||||
});
|
||||
|
||||
it("should return false, when fetch repos is not pending", () => {
|
||||
it('should return false, when fetch repos is not pending', () => {
|
||||
expect(isFetchRepositoryTypesPending({})).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return error when fetch repository types did fail", () => {
|
||||
it('should return error when fetch repository types did fail', () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[FETCH_REPOSITORY_TYPES]: error
|
||||
}
|
||||
[FETCH_REPOSITORY_TYPES]: error,
|
||||
},
|
||||
};
|
||||
expect(getFetchRepositoryTypesFailure(state)).toEqual(error);
|
||||
});
|
||||
|
||||
it("should return undefined when fetch repos did not fail", () => {
|
||||
it('should return undefined when fetch repos did not fail', () => {
|
||||
expect(getFetchRepositoryTypesFailure({})).toBe(undefined);
|
||||
});
|
||||
});
|
||||
@@ -1,28 +1,20 @@
|
||||
// @flow
|
||||
|
||||
import * as types from "../../modules/types";
|
||||
import type {
|
||||
import * as types from '../../modules/types';
|
||||
import {
|
||||
Action,
|
||||
RepositoryType,
|
||||
RepositoryTypeCollection
|
||||
} from "@scm-manager/ui-types";
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
import { isPending } from "../../modules/pending";
|
||||
import { getFailure } from "../../modules/failure";
|
||||
RepositoryTypeCollection,
|
||||
} from '@scm-manager/ui-types';
|
||||
import { apiClient } from '@scm-manager/ui-components';
|
||||
import { isPending } from '../../modules/pending';
|
||||
import { getFailure } from '../../modules/failure';
|
||||
|
||||
export const FETCH_REPOSITORY_TYPES = "scm/repos/FETCH_REPOSITORY_TYPES";
|
||||
export const FETCH_REPOSITORY_TYPES_PENDING = `${FETCH_REPOSITORY_TYPES}_${
|
||||
types.PENDING_SUFFIX
|
||||
}`;
|
||||
export const FETCH_REPOSITORY_TYPES_SUCCESS = `${FETCH_REPOSITORY_TYPES}_${
|
||||
types.SUCCESS_SUFFIX
|
||||
}`;
|
||||
export const FETCH_REPOSITORY_TYPES_FAILURE = `${FETCH_REPOSITORY_TYPES}_${
|
||||
types.FAILURE_SUFFIX
|
||||
}`;
|
||||
export const FETCH_REPOSITORY_TYPES = 'scm/repos/FETCH_REPOSITORY_TYPES';
|
||||
export const FETCH_REPOSITORY_TYPES_PENDING = `${FETCH_REPOSITORY_TYPES}_${types.PENDING_SUFFIX}`;
|
||||
export const FETCH_REPOSITORY_TYPES_SUCCESS = `${FETCH_REPOSITORY_TYPES}_${types.SUCCESS_SUFFIX}`;
|
||||
export const FETCH_REPOSITORY_TYPES_FAILURE = `${FETCH_REPOSITORY_TYPES}_${types.FAILURE_SUFFIX}`;
|
||||
|
||||
export function fetchRepositoryTypesIfNeeded() {
|
||||
return function(dispatch: any, getState: () => Object) {
|
||||
return function(dispatch: any, getState: () => object) {
|
||||
if (shouldFetchRepositoryTypes(getState())) {
|
||||
return fetchRepositoryTypes(dispatch);
|
||||
}
|
||||
@@ -32,7 +24,7 @@ export function fetchRepositoryTypesIfNeeded() {
|
||||
function fetchRepositoryTypes(dispatch: any) {
|
||||
dispatch(fetchRepositoryTypesPending());
|
||||
return apiClient
|
||||
.get("repositoryTypes")
|
||||
.get('repositoryTypes')
|
||||
.then(response => response.json())
|
||||
.then(repositoryTypes => {
|
||||
dispatch(fetchRepositoryTypesSuccess(repositoryTypes));
|
||||
@@ -42,7 +34,7 @@ function fetchRepositoryTypes(dispatch: any) {
|
||||
});
|
||||
}
|
||||
|
||||
export function shouldFetchRepositoryTypes(state: Object) {
|
||||
export function shouldFetchRepositoryTypes(state: object) {
|
||||
if (
|
||||
isFetchRepositoryTypesPending(state) ||
|
||||
getFetchRepositoryTypesFailure(state)
|
||||
@@ -54,23 +46,23 @@ export function shouldFetchRepositoryTypes(state: Object) {
|
||||
|
||||
export function fetchRepositoryTypesPending(): Action {
|
||||
return {
|
||||
type: FETCH_REPOSITORY_TYPES_PENDING
|
||||
type: FETCH_REPOSITORY_TYPES_PENDING,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchRepositoryTypesSuccess(
|
||||
repositoryTypes: RepositoryTypeCollection
|
||||
repositoryTypes: RepositoryTypeCollection,
|
||||
): Action {
|
||||
return {
|
||||
type: FETCH_REPOSITORY_TYPES_SUCCESS,
|
||||
payload: repositoryTypes
|
||||
payload: repositoryTypes,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchRepositoryTypesFailure(error: Error): Action {
|
||||
return {
|
||||
type: FETCH_REPOSITORY_TYPES_FAILURE,
|
||||
payload: error
|
||||
payload: error,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -78,27 +70,29 @@ export function fetchRepositoryTypesFailure(error: Error): Action {
|
||||
|
||||
export default function reducer(
|
||||
state: RepositoryType[] = [],
|
||||
action: Action = { type: "UNKNOWN" }
|
||||
action: Action = {
|
||||
type: 'UNKNOWN',
|
||||
},
|
||||
): RepositoryType[] {
|
||||
if (action.type === FETCH_REPOSITORY_TYPES_SUCCESS && action.payload) {
|
||||
return action.payload._embedded["repositoryTypes"];
|
||||
return action.payload._embedded['repositoryTypes'];
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
// selectors
|
||||
|
||||
export function getRepositoryTypes(state: Object) {
|
||||
export function getRepositoryTypes(state: object) {
|
||||
if (state.repositoryTypes) {
|
||||
return state.repositoryTypes;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function isFetchRepositoryTypesPending(state: Object) {
|
||||
export function isFetchRepositoryTypesPending(state: object) {
|
||||
return isPending(state, FETCH_REPOSITORY_TYPES);
|
||||
}
|
||||
|
||||
export function getFetchRepositoryTypesFailure(state: Object) {
|
||||
export function getFetchRepositoryTypesFailure(state: object) {
|
||||
return getFailure(state, FETCH_REPOSITORY_TYPES);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { Checkbox } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
disabled: boolean,
|
||||
name: string,
|
||||
checked: boolean,
|
||||
onChange?: (value: boolean, name?: string) => void
|
||||
};
|
||||
|
||||
class PermissionCheckbox extends React.Component<Props> {
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<Checkbox
|
||||
key={this.props.name}
|
||||
name={this.props.name}
|
||||
helpText={t("verbs.repository." + this.props.name + ".description")}
|
||||
label={t("verbs.repository." + this.props.name + ".displayName")}
|
||||
checked={this.props.checked}
|
||||
onChange={this.props.onChange}
|
||||
disabled={this.props.disabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("plugins")(PermissionCheckbox);
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import { translate } from 'react-i18next';
|
||||
import { Checkbox } from '@scm-manager/ui-components';
|
||||
|
||||
type Props = {
|
||||
t: (p: string) => string;
|
||||
disabled: boolean;
|
||||
name: string;
|
||||
checked: boolean;
|
||||
onChange?: (value: boolean, name?: string) => void;
|
||||
};
|
||||
|
||||
class PermissionCheckbox extends React.Component<Props> {
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<Checkbox
|
||||
key={this.props.name}
|
||||
name={this.props.name}
|
||||
helpText={t('verbs.repository.' + this.props.name + '.description')}
|
||||
label={t('verbs.repository.' + this.props.name + '.displayName')}
|
||||
checked={this.props.checked}
|
||||
onChange={this.props.onChange}
|
||||
disabled={this.props.disabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate('plugins')(PermissionCheckbox);
|
||||
@@ -1,16 +1,15 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { Select } from "@scm-manager/ui-components";
|
||||
import React from 'react';
|
||||
import { translate } from 'react-i18next';
|
||||
import { Select } from '@scm-manager/ui-components';
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
availableRoles: string[],
|
||||
handleRoleChange: string => void,
|
||||
role: string,
|
||||
label?: string,
|
||||
helpText?: string,
|
||||
loading?: boolean
|
||||
t: (p: string) => string;
|
||||
availableRoles: string[];
|
||||
handleRoleChange: (p: string) => void;
|
||||
role: string;
|
||||
label?: string;
|
||||
helpText?: string;
|
||||
loading?: boolean;
|
||||
};
|
||||
|
||||
class RoleSelector extends React.Component<Props> {
|
||||
@@ -21,19 +20,19 @@ class RoleSelector extends React.Component<Props> {
|
||||
handleRoleChange,
|
||||
loading,
|
||||
label,
|
||||
helpText
|
||||
helpText,
|
||||
} = this.props;
|
||||
|
||||
if (!availableRoles) return null;
|
||||
|
||||
const options = role
|
||||
? this.createSelectOptions(availableRoles)
|
||||
: ["", ...this.createSelectOptions(availableRoles)];
|
||||
: ['', ...this.createSelectOptions(availableRoles)];
|
||||
|
||||
return (
|
||||
<Select
|
||||
onChange={handleRoleChange}
|
||||
value={role ? role : ""}
|
||||
value={role ? role : ''}
|
||||
options={options}
|
||||
loading={loading}
|
||||
label={label}
|
||||
@@ -46,10 +45,10 @@ class RoleSelector extends React.Component<Props> {
|
||||
return roles.map(role => {
|
||||
return {
|
||||
label: role,
|
||||
value: role
|
||||
value: role,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(RoleSelector);
|
||||
export default translate('repos')(RoleSelector);
|
||||
@@ -1,92 +0,0 @@
|
||||
import React from "react";
|
||||
import { shallow, mount } from "@scm-manager/ui-tests/enzyme-router";
|
||||
import "@scm-manager/ui-tests/enzyme";
|
||||
import "@scm-manager/ui-tests/i18n";
|
||||
import DeletePermissionButton from "./DeletePermissionButton";
|
||||
|
||||
import { confirmAlert } from "@scm-manager/ui-components";
|
||||
jest.mock("@scm-manager/ui-components", () => ({
|
||||
confirmAlert: jest.fn(),
|
||||
DeleteButton: require.requireActual("@scm-manager/ui-components").DeleteButton
|
||||
}));
|
||||
|
||||
describe("DeletePermissionButton", () => {
|
||||
|
||||
it("should render nothing, if the delete link is missing", () => {
|
||||
const permission = {
|
||||
_links: {}
|
||||
};
|
||||
|
||||
const navLink = shallow(
|
||||
<DeletePermissionButton
|
||||
permission={permission}
|
||||
deletePermission={() => {}}
|
||||
/>
|
||||
);
|
||||
expect(navLink.text()).toBe("");
|
||||
});
|
||||
|
||||
it("should render the delete icon", () => {
|
||||
const permission = {
|
||||
_links: {
|
||||
delete: {
|
||||
href: "/permission"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const deleteIcon = mount(
|
||||
<DeletePermissionButton
|
||||
permission={permission}
|
||||
deletePermission={() => {}}
|
||||
/>
|
||||
);
|
||||
expect(deleteIcon.html()).not.toBe("");
|
||||
});
|
||||
|
||||
it("should open the confirm dialog on button click", () => {
|
||||
const permission = {
|
||||
_links: {
|
||||
delete: {
|
||||
href: "/permission"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const button = mount(
|
||||
<DeletePermissionButton
|
||||
permission={permission}
|
||||
deletePermission={() => {}}
|
||||
/>
|
||||
);
|
||||
button.find(".fa-trash").simulate("click");
|
||||
|
||||
expect(confirmAlert.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it("should call the delete permission function with delete url", () => {
|
||||
const permission = {
|
||||
_links: {
|
||||
delete: {
|
||||
href: "/permission"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let calledUrl = null;
|
||||
function capture(permission) {
|
||||
calledUrl = permission._links.delete.href;
|
||||
}
|
||||
|
||||
const button = mount(
|
||||
<DeletePermissionButton
|
||||
permission={permission}
|
||||
confirmDialog={false}
|
||||
deletePermission={capture}
|
||||
/>
|
||||
);
|
||||
button.find(".fa-trash").simulate("click");
|
||||
|
||||
expect(calledUrl).toBe("/permission");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
import React from 'react';
|
||||
import { shallow, mount } from '@scm-manager/ui-tests/enzyme-router';
|
||||
import '@scm-manager/ui-tests/enzyme';
|
||||
import '@scm-manager/ui-tests/i18n';
|
||||
import DeletePermissionButton from './DeletePermissionButton';
|
||||
|
||||
import { confirmAlert } from '@scm-manager/ui-components';
|
||||
jest.mock('@scm-manager/ui-components', () => ({
|
||||
confirmAlert: jest.fn(),
|
||||
DeleteButton: require.requireActual('@scm-manager/ui-components')
|
||||
.DeleteButton,
|
||||
}));
|
||||
|
||||
describe('DeletePermissionButton', () => {
|
||||
it('should render nothing, if the delete link is missing', () => {
|
||||
const permission = {
|
||||
_links: {},
|
||||
};
|
||||
|
||||
const navLink = shallow(
|
||||
<DeletePermissionButton
|
||||
permission={permission}
|
||||
deletePermission={() => {}}
|
||||
/>,
|
||||
);
|
||||
expect(navLink.text()).toBe('');
|
||||
});
|
||||
|
||||
it('should render the delete icon', () => {
|
||||
const permission = {
|
||||
_links: {
|
||||
delete: {
|
||||
href: '/permission',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const deleteIcon = mount(
|
||||
<DeletePermissionButton
|
||||
permission={permission}
|
||||
deletePermission={() => {}}
|
||||
/>,
|
||||
);
|
||||
expect(deleteIcon.html()).not.toBe('');
|
||||
});
|
||||
|
||||
it('should open the confirm dialog on button click', () => {
|
||||
const permission = {
|
||||
_links: {
|
||||
delete: {
|
||||
href: '/permission',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const button = mount(
|
||||
<DeletePermissionButton
|
||||
permission={permission}
|
||||
deletePermission={() => {}}
|
||||
/>,
|
||||
);
|
||||
button.find('.fa-trash').simulate('click');
|
||||
|
||||
expect(confirmAlert.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should call the delete permission function with delete url', () => {
|
||||
const permission = {
|
||||
_links: {
|
||||
delete: {
|
||||
href: '/permission',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let calledUrl = null;
|
||||
function capture(permission) {
|
||||
calledUrl = permission._links.delete.href;
|
||||
}
|
||||
|
||||
const button = mount(
|
||||
<DeletePermissionButton
|
||||
permission={permission}
|
||||
confirmDialog={false}
|
||||
deletePermission={capture}
|
||||
/>,
|
||||
);
|
||||
button.find('.fa-trash').simulate('click');
|
||||
|
||||
expect(calledUrl).toBe('/permission');
|
||||
});
|
||||
});
|
||||
@@ -1,51 +1,50 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { Permission } from "@scm-manager/ui-types";
|
||||
import { confirmAlert } from "@scm-manager/ui-components";
|
||||
import React from 'react';
|
||||
import { translate } from 'react-i18next';
|
||||
import { Permission } from '@scm-manager/ui-types';
|
||||
import { confirmAlert } from '@scm-manager/ui-components';
|
||||
|
||||
type Props = {
|
||||
permission: Permission,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
confirmDialog?: boolean,
|
||||
t: string => string,
|
||||
permission: Permission;
|
||||
namespace: string;
|
||||
repoName: string;
|
||||
confirmDialog?: boolean;
|
||||
t: (p: string) => string;
|
||||
deletePermission: (
|
||||
permission: Permission,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
) => void,
|
||||
loading: boolean
|
||||
repoName: string,
|
||||
) => void;
|
||||
loading: boolean;
|
||||
};
|
||||
|
||||
class DeletePermissionButton extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
confirmDialog: true
|
||||
confirmDialog: true,
|
||||
};
|
||||
|
||||
deletePermission = () => {
|
||||
this.props.deletePermission(
|
||||
this.props.permission,
|
||||
this.props.namespace,
|
||||
this.props.repoName
|
||||
this.props.repoName,
|
||||
);
|
||||
};
|
||||
|
||||
confirmDelete = () => {
|
||||
const { t } = this.props;
|
||||
confirmAlert({
|
||||
title: t("permission.delete-permission-button.confirm-alert.title"),
|
||||
message: t("permission.delete-permission-button.confirm-alert.message"),
|
||||
title: t('permission.delete-permission-button.confirm-alert.title'),
|
||||
message: t('permission.delete-permission-button.confirm-alert.message'),
|
||||
buttons: [
|
||||
{
|
||||
label: t("permission.delete-permission-button.confirm-alert.submit"),
|
||||
onClick: () => this.deletePermission()
|
||||
label: t('permission.delete-permission-button.confirm-alert.submit'),
|
||||
onClick: () => this.deletePermission(),
|
||||
},
|
||||
{
|
||||
label: t("permission.delete-permission-button.confirm-alert.cancel"),
|
||||
onClick: () => null
|
||||
}
|
||||
]
|
||||
label: t('permission.delete-permission-button.confirm-alert.cancel'),
|
||||
onClick: () => null,
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
@@ -70,4 +69,4 @@ class DeletePermissionButton extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(DeletePermissionButton);
|
||||
export default translate('repos')(DeletePermissionButton);
|
||||
@@ -1,70 +1,69 @@
|
||||
//@flow
|
||||
import * as validator from "./permissionValidation";
|
||||
import * as validator from './permissionValidation';
|
||||
|
||||
describe("permission validation", () => {
|
||||
it("should return true if permission is valid and does not exist", () => {
|
||||
describe('permission validation', () => {
|
||||
it('should return true if permission is valid and does not exist', () => {
|
||||
const permissions = [];
|
||||
const name = "PermissionName";
|
||||
const name = 'PermissionName';
|
||||
const groupPermission = false;
|
||||
|
||||
expect(
|
||||
validator.isPermissionValid(name, groupPermission, permissions)
|
||||
validator.isPermissionValid(name, groupPermission, permissions),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true if permission is valid and does not exists with same group permission", () => {
|
||||
it('should return true if permission is valid and does not exists with same group permission', () => {
|
||||
const permissions = [
|
||||
{
|
||||
name: "PermissionName",
|
||||
name: 'PermissionName',
|
||||
groupPermission: true,
|
||||
type: "READ",
|
||||
type: 'READ',
|
||||
_links: {},
|
||||
verbs: []
|
||||
}
|
||||
verbs: [],
|
||||
},
|
||||
];
|
||||
const name = "PermissionName";
|
||||
const name = 'PermissionName';
|
||||
const groupPermission = false;
|
||||
|
||||
expect(
|
||||
validator.isPermissionValid(name, groupPermission, permissions)
|
||||
validator.isPermissionValid(name, groupPermission, permissions),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false if permission is valid but exists", () => {
|
||||
it('should return false if permission is valid but exists', () => {
|
||||
const permissions = [
|
||||
{
|
||||
name: "PermissionName",
|
||||
name: 'PermissionName',
|
||||
groupPermission: false,
|
||||
type: "READ",
|
||||
type: 'READ',
|
||||
_links: {},
|
||||
verbs: []
|
||||
}
|
||||
verbs: [],
|
||||
},
|
||||
];
|
||||
const name = "PermissionName";
|
||||
const name = 'PermissionName';
|
||||
const groupPermission = false;
|
||||
|
||||
expect(
|
||||
validator.isPermissionValid(name, groupPermission, permissions)
|
||||
validator.isPermissionValid(name, groupPermission, permissions),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false if permission does not exist but is invalid", () => {
|
||||
it('should return false if permission does not exist but is invalid', () => {
|
||||
const permissions = [];
|
||||
const name = "@PermissionName";
|
||||
const name = '@PermissionName';
|
||||
const groupPermission = false;
|
||||
|
||||
expect(
|
||||
validator.isPermissionValid(name, groupPermission, permissions)
|
||||
validator.isPermissionValid(name, groupPermission, permissions),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false if permission is not valid and does not exist", () => {
|
||||
it('should return false if permission is not valid and does not exist', () => {
|
||||
const permissions = [];
|
||||
const name = "@PermissionName";
|
||||
const name = '@PermissionName';
|
||||
const groupPermission = false;
|
||||
|
||||
expect(
|
||||
validator.isPermissionValid(name, groupPermission, permissions)
|
||||
validator.isPermissionValid(name, groupPermission, permissions),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,5 @@
|
||||
// @flow
|
||||
import {validation} from "@scm-manager/ui-components";
|
||||
import type {PermissionCollection} from "@scm-manager/ui-types";
|
||||
import { validation } from '@scm-manager/ui-components';
|
||||
import { PermissionCollection } from '@scm-manager/ui-types';
|
||||
|
||||
const isNameValid = validation.isNameValid;
|
||||
|
||||
@@ -9,7 +8,7 @@ export { isNameValid };
|
||||
export const isPermissionValid = (
|
||||
name: string,
|
||||
groupPermission: boolean,
|
||||
permissions: PermissionCollection
|
||||
permissions: PermissionCollection,
|
||||
) => {
|
||||
return (
|
||||
isNameValid(name) &&
|
||||
@@ -20,7 +19,7 @@ export const isPermissionValid = (
|
||||
const currentPermissionIncludeName = (
|
||||
name: string,
|
||||
groupPermission: boolean,
|
||||
permissions: PermissionCollection
|
||||
permissions: PermissionCollection,
|
||||
) => {
|
||||
for (let i = 0; i < permissions.length; i++) {
|
||||
if (
|
||||
@@ -1,27 +1,26 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
import {
|
||||
ButtonGroup,
|
||||
Button,
|
||||
SubmitButton,
|
||||
Modal
|
||||
} from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
import PermissionCheckbox from "../components/PermissionCheckbox";
|
||||
Modal,
|
||||
} from '@scm-manager/ui-components';
|
||||
import { translate } from 'react-i18next';
|
||||
import PermissionCheckbox from '../components/PermissionCheckbox';
|
||||
|
||||
type Props = {
|
||||
readOnly: boolean,
|
||||
availableVerbs: string[],
|
||||
selectedVerbs: string[],
|
||||
onSubmit: (string[]) => void,
|
||||
onClose: () => void,
|
||||
readOnly: boolean;
|
||||
availableVerbs: string[];
|
||||
selectedVerbs: string[];
|
||||
onSubmit: (p: string[]) => void;
|
||||
onClose: () => void;
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
t: (p: string) => string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
verbs: any
|
||||
verbs: any;
|
||||
};
|
||||
|
||||
class AdvancedPermissionsDialog extends React.Component<Props, State> {
|
||||
@@ -33,9 +32,11 @@ class AdvancedPermissionsDialog extends React.Component<Props, State> {
|
||||
verb =>
|
||||
(verbs[verb] = props.selectedVerbs
|
||||
? props.selectedVerbs.includes(verb)
|
||||
: false)
|
||||
: false),
|
||||
);
|
||||
this.state = { verbs };
|
||||
this.state = {
|
||||
verbs,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -53,7 +54,7 @@ class AdvancedPermissionsDialog extends React.Component<Props, State> {
|
||||
));
|
||||
|
||||
const submitButton = !readOnly ? (
|
||||
<SubmitButton label={t("permission.advanced.dialog.submit")} />
|
||||
<SubmitButton label={t('permission.advanced.dialog.submit')} />
|
||||
) : null;
|
||||
|
||||
const body = <>{verbSelectBoxes}</>;
|
||||
@@ -63,7 +64,7 @@ class AdvancedPermissionsDialog extends React.Component<Props, State> {
|
||||
<ButtonGroup>
|
||||
{submitButton}
|
||||
<Button
|
||||
label={t("permission.advanced.dialog.abort")}
|
||||
label={t('permission.advanced.dialog.abort')}
|
||||
action={onClose}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
@@ -72,7 +73,7 @@ class AdvancedPermissionsDialog extends React.Component<Props, State> {
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t("permission.advanced.dialog.title")}
|
||||
title={t('permission.advanced.dialog.title')}
|
||||
closeFunction={() => onClose()}
|
||||
body={body}
|
||||
footer={footer}
|
||||
@@ -83,17 +84,22 @@ class AdvancedPermissionsDialog extends React.Component<Props, State> {
|
||||
|
||||
handleChange = (value: boolean, name: string) => {
|
||||
const { verbs } = this.state;
|
||||
const newVerbs = { ...verbs, [name]: value };
|
||||
this.setState({ verbs: newVerbs });
|
||||
const newVerbs = {
|
||||
...verbs,
|
||||
[name]: value,
|
||||
};
|
||||
this.setState({
|
||||
verbs: newVerbs,
|
||||
});
|
||||
};
|
||||
|
||||
onSubmit = () => {
|
||||
this.props.onSubmit(
|
||||
Object.entries(this.state.verbs)
|
||||
.filter(e => e[1])
|
||||
.map(e => e[0])
|
||||
.map(e => e[0]),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("repos")(AdvancedPermissionsDialog);
|
||||
export default translate('repos')(AdvancedPermissionsDialog);
|
||||
@@ -1,7 +1,11 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import {translate} from "react-i18next";
|
||||
import type {PermissionCollection, PermissionCreateEntry, RepositoryRole, SelectValue} from "@scm-manager/ui-types";
|
||||
import React from 'react';
|
||||
import { translate } from 'react-i18next';
|
||||
import {
|
||||
PermissionCollection,
|
||||
PermissionCreateEntry,
|
||||
RepositoryRole,
|
||||
SelectValue,
|
||||
} from '@scm-manager/ui-types';
|
||||
import {
|
||||
Button,
|
||||
GroupAutocomplete,
|
||||
@@ -9,34 +13,34 @@ import {
|
||||
Radio,
|
||||
SubmitButton,
|
||||
Subtitle,
|
||||
UserAutocomplete
|
||||
} from "@scm-manager/ui-components";
|
||||
import * as validator from "../components/permissionValidation";
|
||||
import RoleSelector from "../components/RoleSelector";
|
||||
import AdvancedPermissionsDialog from "./AdvancedPermissionsDialog";
|
||||
import {findVerbsForRole} from "../modules/permissions";
|
||||
UserAutocomplete,
|
||||
} from '@scm-manager/ui-components';
|
||||
import * as validator from '../components/permissionValidation';
|
||||
import RoleSelector from '../components/RoleSelector';
|
||||
import AdvancedPermissionsDialog from './AdvancedPermissionsDialog';
|
||||
import { findVerbsForRole } from '../modules/permissions';
|
||||
|
||||
type Props = {
|
||||
availableRoles: RepositoryRole[],
|
||||
availableVerbs: string[],
|
||||
createPermission: (permission: PermissionCreateEntry) => void,
|
||||
loading: boolean,
|
||||
currentPermissions: PermissionCollection,
|
||||
groupAutocompleteLink: string,
|
||||
userAutocompleteLink: string,
|
||||
availableRoles: RepositoryRole[];
|
||||
availableVerbs: string[];
|
||||
createPermission: (permission: PermissionCreateEntry) => void;
|
||||
loading: boolean;
|
||||
currentPermissions: PermissionCollection;
|
||||
groupAutocompleteLink: string;
|
||||
userAutocompleteLink: string;
|
||||
|
||||
// Context props
|
||||
t: string => string
|
||||
t: (p: string) => string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
name: string,
|
||||
role?: string,
|
||||
verbs?: string[],
|
||||
groupPermission: boolean,
|
||||
valid: boolean,
|
||||
value?: SelectValue,
|
||||
showAdvancedDialog: boolean
|
||||
name: string;
|
||||
role?: string;
|
||||
verbs?: string[];
|
||||
groupPermission: boolean;
|
||||
valid: boolean;
|
||||
value?: SelectValue;
|
||||
showAdvancedDialog: boolean;
|
||||
};
|
||||
|
||||
class CreatePermissionForm extends React.Component<Props, State> {
|
||||
@@ -44,23 +48,23 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
name: "",
|
||||
name: '',
|
||||
role: props.availableRoles[0].name,
|
||||
verbs: undefined,
|
||||
groupPermission: false,
|
||||
valid: true,
|
||||
value: undefined,
|
||||
showAdvancedDialog: false
|
||||
showAdvancedDialog: false,
|
||||
};
|
||||
}
|
||||
|
||||
permissionScopeChanged = event => {
|
||||
const groupPermission = event.target.value === "GROUP_PERMISSION";
|
||||
const groupPermission = event.target.value === 'GROUP_PERMISSION';
|
||||
this.setState({
|
||||
value: undefined,
|
||||
name: "",
|
||||
name: '',
|
||||
groupPermission: groupPermission,
|
||||
valid: false
|
||||
valid: false,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -71,7 +75,7 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
<GroupAutocomplete
|
||||
autocompleteLink={this.props.groupAutocompleteLink}
|
||||
valueSelected={this.selectName}
|
||||
value={this.state.value ? this.state.value : ""}
|
||||
value={this.state.value ? this.state.value : ''}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -79,7 +83,7 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
<UserAutocomplete
|
||||
autocompleteLink={this.props.userAutocompleteLink}
|
||||
valueSelected={this.selectName}
|
||||
value={this.state.value ? this.state.value : ""}
|
||||
value={this.state.value ? this.state.value : ''}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -91,8 +95,8 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
valid: validator.isPermissionValid(
|
||||
value.value.id,
|
||||
this.state.groupPermission,
|
||||
this.props.currentPermissions
|
||||
)
|
||||
this.props.currentPermissions,
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -117,7 +121,7 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
<>
|
||||
<hr />
|
||||
<Subtitle
|
||||
subtitle={t("permission.add-permission.add-permission-heading")}
|
||||
subtitle={t('permission.add-permission.add-permission-heading')}
|
||||
/>
|
||||
{advancedDialog}
|
||||
<form onSubmit={this.submit}>
|
||||
@@ -127,14 +131,14 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
name="permission_scope"
|
||||
value="USER_PERMISSION"
|
||||
checked={!this.state.groupPermission}
|
||||
label={t("permission.user-permission")}
|
||||
label={t('permission.user-permission')}
|
||||
onChange={this.permissionScopeChanged}
|
||||
/>
|
||||
<Radio
|
||||
name="permission_scope"
|
||||
value="GROUP_PERMISSION"
|
||||
checked={this.state.groupPermission}
|
||||
label={t("permission.group-permission")}
|
||||
label={t('permission.group-permission')}
|
||||
onChange={this.permissionScopeChanged}
|
||||
/>
|
||||
</div>
|
||||
@@ -148,19 +152,19 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
<div className="column is-narrow">
|
||||
<RoleSelector
|
||||
availableRoles={availableRoleNames}
|
||||
label={t("permission.role")}
|
||||
helpText={t("permission.help.roleHelpText")}
|
||||
label={t('permission.role')}
|
||||
helpText={t('permission.help.roleHelpText')}
|
||||
handleRoleChange={this.handleRoleChange}
|
||||
role={role}
|
||||
/>
|
||||
</div>
|
||||
<div className="column">
|
||||
<LabelWithHelpIcon
|
||||
label={t("permission.permissions")}
|
||||
helpText={t("permission.help.permissionsHelpText")}
|
||||
label={t('permission.permissions')}
|
||||
helpText={t('permission.help.permissionsHelpText')}
|
||||
/>
|
||||
<Button
|
||||
label={t("permission.advanced-button.label")}
|
||||
label={t('permission.advanced-button.label')}
|
||||
action={this.toggleAdvancedPermissionsDialog}
|
||||
/>
|
||||
</div>
|
||||
@@ -170,9 +174,9 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
<div className="columns">
|
||||
<div className="column">
|
||||
<SubmitButton
|
||||
label={t("permission.add-permission.submit-button")}
|
||||
label={t('permission.add-permission.submit-button')}
|
||||
loading={loading}
|
||||
disabled={!this.state.valid || this.state.name === ""}
|
||||
disabled={!this.state.valid || this.state.name === ''}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -183,7 +187,7 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
|
||||
toggleAdvancedPermissionsDialog = () => {
|
||||
this.setState(prevState => ({
|
||||
showAdvancedDialog: !prevState.showAdvancedDialog
|
||||
showAdvancedDialog: !prevState.showAdvancedDialog,
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -191,7 +195,7 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
this.setState({
|
||||
showAdvancedDialog: false,
|
||||
role: undefined,
|
||||
verbs: newVerbs
|
||||
verbs: newVerbs,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -200,7 +204,7 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
name: this.state.name,
|
||||
role: this.state.role,
|
||||
verbs: this.state.verbs,
|
||||
groupPermission: this.state.groupPermission
|
||||
groupPermission: this.state.groupPermission,
|
||||
});
|
||||
this.removeState();
|
||||
e.preventDefault();
|
||||
@@ -208,11 +212,11 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
|
||||
removeState = () => {
|
||||
this.setState({
|
||||
name: "",
|
||||
name: '',
|
||||
role: this.props.availableRoles[0].name,
|
||||
verbs: undefined,
|
||||
valid: true,
|
||||
value: undefined
|
||||
value: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -223,7 +227,7 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
}
|
||||
this.setState({
|
||||
role: selectedRole.name,
|
||||
verbs: []
|
||||
verbs: [],
|
||||
});
|
||||
};
|
||||
|
||||
@@ -232,4 +236,4 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("repos")(CreatePermissionForm);
|
||||
export default translate('repos')(CreatePermissionForm);
|
||||
@@ -1,7 +1,6 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import {connect} from "react-redux";
|
||||
import {translate} from "react-i18next";
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { translate } from 'react-i18next';
|
||||
import {
|
||||
createPermission,
|
||||
createPermissionReset,
|
||||
@@ -21,58 +20,68 @@ import {
|
||||
isCreatePermissionPending,
|
||||
isFetchAvailablePermissionsPending,
|
||||
isFetchPermissionsPending,
|
||||
modifyPermissionReset
|
||||
} from "../modules/permissions";
|
||||
import {ErrorPage, LabelWithHelpIcon, Loading, Subtitle} from "@scm-manager/ui-components";
|
||||
import type {Permission, PermissionCollection, PermissionCreateEntry, RepositoryRole} from "@scm-manager/ui-types";
|
||||
import SinglePermission from "./SinglePermission";
|
||||
import CreatePermissionForm from "./CreatePermissionForm";
|
||||
import type {History} from "history";
|
||||
import {getPermissionsLink} from "../../modules/repos";
|
||||
modifyPermissionReset,
|
||||
} from '../modules/permissions';
|
||||
import {
|
||||
ErrorPage,
|
||||
LabelWithHelpIcon,
|
||||
Loading,
|
||||
Subtitle,
|
||||
} from '@scm-manager/ui-components';
|
||||
import {
|
||||
Permission,
|
||||
PermissionCollection,
|
||||
PermissionCreateEntry,
|
||||
RepositoryRole,
|
||||
} from '@scm-manager/ui-types';
|
||||
import SinglePermission from './SinglePermission';
|
||||
import CreatePermissionForm from './CreatePermissionForm';
|
||||
import { History } from 'history';
|
||||
import { getPermissionsLink } from '../../modules/repos';
|
||||
import {
|
||||
getGroupAutoCompleteLink,
|
||||
getRepositoryRolesLink,
|
||||
getRepositoryVerbsLink,
|
||||
getUserAutoCompleteLink
|
||||
} from "../../../modules/indexResource";
|
||||
getUserAutoCompleteLink,
|
||||
} from '../../../modules/indexResource';
|
||||
|
||||
type Props = {
|
||||
availablePermissions: boolean,
|
||||
availableRepositoryRoles: RepositoryRole[],
|
||||
availableVerbs: string[],
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
permissions: PermissionCollection,
|
||||
hasPermissionToCreate: boolean,
|
||||
loadingCreatePermission: boolean,
|
||||
repositoryRolesLink: string,
|
||||
repositoryVerbsLink: string,
|
||||
permissionsLink: string,
|
||||
groupAutocompleteLink: string,
|
||||
userAutocompleteLink: string,
|
||||
availablePermissions: boolean;
|
||||
availableRepositoryRoles: RepositoryRole[];
|
||||
availableVerbs: string[];
|
||||
namespace: string;
|
||||
repoName: string;
|
||||
loading: boolean;
|
||||
error: Error;
|
||||
permissions: PermissionCollection;
|
||||
hasPermissionToCreate: boolean;
|
||||
loadingCreatePermission: boolean;
|
||||
repositoryRolesLink: string;
|
||||
repositoryVerbsLink: string;
|
||||
permissionsLink: string;
|
||||
groupAutocompleteLink: string;
|
||||
userAutocompleteLink: string;
|
||||
|
||||
//dispatch functions
|
||||
fetchAvailablePermissionsIfNeeded: (
|
||||
repositoryRolesLink: string,
|
||||
repositoryVerbsLink: string
|
||||
) => void,
|
||||
fetchPermissions: (link: string, namespace: string, repoName: string) => void,
|
||||
repositoryVerbsLink: string,
|
||||
) => void;
|
||||
fetchPermissions: (link: string, namespace: string, repoName: string) => void;
|
||||
createPermission: (
|
||||
link: string,
|
||||
permission: PermissionCreateEntry,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
callback?: () => void
|
||||
) => void,
|
||||
createPermissionReset: (string, string) => void,
|
||||
modifyPermissionReset: (string, string) => void,
|
||||
deletePermissionReset: (string, string) => void,
|
||||
callback?: () => void,
|
||||
) => void;
|
||||
createPermissionReset: (p1: string, p2: string) => void;
|
||||
modifyPermissionReset: (p1: string, p2: string) => void;
|
||||
deletePermissionReset: (p1: string, p2: string) => void;
|
||||
// context props
|
||||
t: string => string,
|
||||
match: any,
|
||||
history: History
|
||||
t: (p: string) => string;
|
||||
match: any;
|
||||
history: History;
|
||||
};
|
||||
|
||||
class Permissions extends React.Component<Props> {
|
||||
@@ -87,7 +96,7 @@ class Permissions extends React.Component<Props> {
|
||||
deletePermissionReset,
|
||||
permissionsLink,
|
||||
repositoryRolesLink,
|
||||
repositoryVerbsLink
|
||||
repositoryVerbsLink,
|
||||
} = this.props;
|
||||
|
||||
createPermissionReset(namespace, repoName);
|
||||
@@ -102,7 +111,7 @@ class Permissions extends React.Component<Props> {
|
||||
this.props.permissionsLink,
|
||||
permission,
|
||||
this.props.namespace,
|
||||
this.props.repoName
|
||||
this.props.repoName,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -120,13 +129,13 @@ class Permissions extends React.Component<Props> {
|
||||
loadingCreatePermission,
|
||||
hasPermissionToCreate,
|
||||
userAutocompleteLink,
|
||||
groupAutocompleteLink
|
||||
groupAutocompleteLink,
|
||||
} = this.props;
|
||||
if (error) {
|
||||
return (
|
||||
<ErrorPage
|
||||
title={t("permission.error-title")}
|
||||
subtitle={t("permission.error-subtitle")}
|
||||
title={t('permission.error-title')}
|
||||
subtitle={t('permission.error-subtitle')}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
@@ -150,26 +159,26 @@ class Permissions extends React.Component<Props> {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Subtitle subtitle={t("permission.title")} />
|
||||
<Subtitle subtitle={t('permission.title')} />
|
||||
<table className="card-table table is-hoverable is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<LabelWithHelpIcon
|
||||
label={t("permission.name")}
|
||||
helpText={t("permission.help.nameHelpText")}
|
||||
label={t('permission.name')}
|
||||
helpText={t('permission.help.nameHelpText')}
|
||||
/>
|
||||
</th>
|
||||
<th>
|
||||
<LabelWithHelpIcon
|
||||
label={t("permission.role")}
|
||||
helpText={t("permission.help.roleHelpText")}
|
||||
label={t('permission.role')}
|
||||
helpText={t('permission.help.roleHelpText')}
|
||||
/>
|
||||
</th>
|
||||
<th>
|
||||
<LabelWithHelpIcon
|
||||
label={t("permission.permissions")}
|
||||
helpText={t("permission.help.permissionsHelpText")}
|
||||
label={t('permission.permissions')}
|
||||
helpText={t('permission.help.permissionsHelpText')}
|
||||
/>
|
||||
</th>
|
||||
<th />
|
||||
@@ -212,7 +221,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
const loadingCreatePermission = isCreatePermissionPending(
|
||||
state,
|
||||
namespace,
|
||||
repoName
|
||||
repoName,
|
||||
);
|
||||
const hasPermissionToCreate = hasCreatePermission(state, namespace, repoName);
|
||||
const repositoryRolesLink = getRepositoryRolesLink(state);
|
||||
@@ -239,7 +248,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
loadingCreatePermission,
|
||||
permissionsLink,
|
||||
groupAutocompleteLink,
|
||||
userAutocompleteLink
|
||||
userAutocompleteLink,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -250,13 +259,13 @@ const mapDispatchToProps = dispatch => {
|
||||
},
|
||||
fetchAvailablePermissionsIfNeeded: (
|
||||
repositoryRolesLink: string,
|
||||
repositoryVerbsLink: string
|
||||
repositoryVerbsLink: string,
|
||||
) => {
|
||||
dispatch(
|
||||
fetchAvailablePermissionsIfNeeded(
|
||||
repositoryRolesLink,
|
||||
repositoryVerbsLink
|
||||
)
|
||||
repositoryVerbsLink,
|
||||
),
|
||||
);
|
||||
},
|
||||
createPermission: (
|
||||
@@ -264,10 +273,10 @@ const mapDispatchToProps = dispatch => {
|
||||
permission: PermissionCreateEntry,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
callback?: () => void
|
||||
callback?: () => void,
|
||||
) => {
|
||||
dispatch(
|
||||
createPermission(link, permission, namespace, repoName, callback)
|
||||
createPermission(link, permission, namespace, repoName, callback),
|
||||
);
|
||||
},
|
||||
createPermissionReset: (namespace: string, repoName: string) => {
|
||||
@@ -278,11 +287,11 @@ const mapDispatchToProps = dispatch => {
|
||||
},
|
||||
deletePermissionReset: (namespace: string, repoName: string) => {
|
||||
dispatch(deletePermissionReset(namespace, repoName));
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("repos")(Permissions));
|
||||
mapDispatchToProps,
|
||||
)(translate('repos')(Permissions));
|
||||
@@ -1,49 +1,48 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import type { History } from "history";
|
||||
import { translate } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import type { RepositoryRole, Permission } from "@scm-manager/ui-types";
|
||||
import { Button, Icon } from "@scm-manager/ui-components";
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { History } from 'history';
|
||||
import { translate } from 'react-i18next';
|
||||
import styled from 'styled-components';
|
||||
import { RepositoryRole, Permission } from '@scm-manager/ui-types';
|
||||
import { Button, Icon } from '@scm-manager/ui-components';
|
||||
import {
|
||||
modifyPermission,
|
||||
isModifyPermissionPending,
|
||||
deletePermission,
|
||||
isDeletePermissionPending,
|
||||
findVerbsForRole
|
||||
} from "../modules/permissions";
|
||||
import DeletePermissionButton from "../components/buttons/DeletePermissionButton";
|
||||
import RoleSelector from "../components/RoleSelector";
|
||||
import AdvancedPermissionsDialog from "./AdvancedPermissionsDialog";
|
||||
findVerbsForRole,
|
||||
} from '../modules/permissions';
|
||||
import DeletePermissionButton from '../components/buttons/DeletePermissionButton';
|
||||
import RoleSelector from '../components/RoleSelector';
|
||||
import AdvancedPermissionsDialog from './AdvancedPermissionsDialog';
|
||||
|
||||
type Props = {
|
||||
availableRepositoryRoles: RepositoryRole[],
|
||||
availableRepositoryVerbs: string[],
|
||||
submitForm: Permission => void,
|
||||
availableRepositoryRoles: RepositoryRole[];
|
||||
availableRepositoryVerbs: string[];
|
||||
submitForm: (p: Permission) => void;
|
||||
modifyPermission: (
|
||||
permission: Permission,
|
||||
namespace: string,
|
||||
name: string
|
||||
) => void,
|
||||
permission: Permission,
|
||||
t: string => string,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
match: any,
|
||||
history: History,
|
||||
loading: boolean,
|
||||
name: string,
|
||||
) => void;
|
||||
permission: Permission;
|
||||
t: (p: string) => string;
|
||||
namespace: string;
|
||||
repoName: string;
|
||||
match: any;
|
||||
history: History;
|
||||
loading: boolean;
|
||||
deletePermission: (
|
||||
permission: Permission,
|
||||
namespace: string,
|
||||
name: string
|
||||
) => void,
|
||||
deleteLoading: boolean
|
||||
name: string,
|
||||
) => void;
|
||||
deleteLoading: boolean;
|
||||
};
|
||||
|
||||
type State = {
|
||||
permission: Permission,
|
||||
showAdvancedDialog: boolean
|
||||
permission: Permission;
|
||||
showAdvancedDialog: boolean;
|
||||
};
|
||||
|
||||
const FullWidthTr = styled.tr`
|
||||
@@ -65,13 +64,13 @@ class SinglePermission extends React.Component<Props, State> {
|
||||
|
||||
this.state = {
|
||||
permission: {
|
||||
name: "",
|
||||
name: '',
|
||||
role: undefined,
|
||||
verbs: defaultPermission.verbs,
|
||||
groupPermission: false,
|
||||
_links: {}
|
||||
_links: {},
|
||||
},
|
||||
showAdvancedDialog: false
|
||||
showAdvancedDialog: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -85,8 +84,8 @@ class SinglePermission extends React.Component<Props, State> {
|
||||
role: permission.role,
|
||||
verbs: permission.verbs,
|
||||
groupPermission: permission.groupPermission,
|
||||
_links: permission._links
|
||||
}
|
||||
_links: permission._links,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -95,7 +94,7 @@ class SinglePermission extends React.Component<Props, State> {
|
||||
this.props.deletePermission(
|
||||
this.props.permission,
|
||||
this.props.namespace,
|
||||
this.props.repoName
|
||||
this.props.repoName,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -106,14 +105,14 @@ class SinglePermission extends React.Component<Props, State> {
|
||||
loading,
|
||||
namespace,
|
||||
repoName,
|
||||
t
|
||||
t,
|
||||
} = this.props;
|
||||
const { permission, showAdvancedDialog } = this.state;
|
||||
const availableRoleNames =
|
||||
!!availableRepositoryRoles && availableRepositoryRoles.map(r => r.name);
|
||||
const readOnly = !this.mayChangePermissions();
|
||||
const roleSelector = readOnly ? (
|
||||
<td>{permission.role ? permission.role : t("permission.custom")}</td>
|
||||
<td>{permission.role ? permission.role : t('permission.custom')}</td>
|
||||
) : (
|
||||
<td>
|
||||
<RoleSelector
|
||||
@@ -141,9 +140,9 @@ class SinglePermission extends React.Component<Props, State> {
|
||||
|
||||
const iconType =
|
||||
permission && permission.groupPermission ? (
|
||||
<Icon title={t("permission.group")} name="user-friends" />
|
||||
<Icon title={t('permission.group')} name="user-friends" />
|
||||
) : (
|
||||
<Icon title={t("permission.user")} name="user" />
|
||||
<Icon title={t('permission.user')} name="user" />
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -154,7 +153,7 @@ class SinglePermission extends React.Component<Props, State> {
|
||||
{roleSelector}
|
||||
<VCenteredTd>
|
||||
<Button
|
||||
label={t("permission.advanced-button.label")}
|
||||
label={t('permission.advanced-button.label')}
|
||||
action={this.handleDetailedPermissionsPressed}
|
||||
/>
|
||||
</VCenteredTd>
|
||||
@@ -177,11 +176,15 @@ class SinglePermission extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
handleDetailedPermissionsPressed = () => {
|
||||
this.setState({ showAdvancedDialog: true });
|
||||
this.setState({
|
||||
showAdvancedDialog: true,
|
||||
});
|
||||
};
|
||||
|
||||
closeAdvancedPermissionsDialog = () => {
|
||||
this.setState({ showAdvancedDialog: false });
|
||||
this.setState({
|
||||
showAdvancedDialog: false,
|
||||
});
|
||||
};
|
||||
|
||||
submitAdvancedPermissionsDialog = (newVerbs: string[]) => {
|
||||
@@ -189,9 +192,13 @@ class SinglePermission extends React.Component<Props, State> {
|
||||
this.setState(
|
||||
{
|
||||
showAdvancedDialog: false,
|
||||
permission: { ...permission, role: undefined, verbs: newVerbs }
|
||||
permission: {
|
||||
...permission,
|
||||
role: undefined,
|
||||
verbs: newVerbs,
|
||||
},
|
||||
},
|
||||
() => this.modifyPermissionVerbs(newVerbs)
|
||||
() => this.modifyPermissionVerbs(newVerbs),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -199,9 +206,13 @@ class SinglePermission extends React.Component<Props, State> {
|
||||
const { permission } = this.state;
|
||||
this.setState(
|
||||
{
|
||||
permission: { ...permission, role: role, verbs: undefined }
|
||||
permission: {
|
||||
...permission,
|
||||
role: role,
|
||||
verbs: undefined,
|
||||
},
|
||||
},
|
||||
() => this.modifyPermissionRole(role)
|
||||
() => this.modifyPermissionRole(role),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -216,7 +227,7 @@ class SinglePermission extends React.Component<Props, State> {
|
||||
this.props.modifyPermission(
|
||||
permission,
|
||||
this.props.namespace,
|
||||
this.props.repoName
|
||||
this.props.repoName,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -226,7 +237,7 @@ class SinglePermission extends React.Component<Props, State> {
|
||||
this.props.modifyPermission(
|
||||
permission,
|
||||
this.props.namespace,
|
||||
this.props.repoName
|
||||
this.props.repoName,
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -237,16 +248,19 @@ const mapStateToProps = (state, ownProps) => {
|
||||
state,
|
||||
ownProps.namespace,
|
||||
ownProps.repoName,
|
||||
permission
|
||||
permission,
|
||||
);
|
||||
const deleteLoading = isDeletePermissionPending(
|
||||
state,
|
||||
ownProps.namespace,
|
||||
ownProps.repoName,
|
||||
permission
|
||||
permission,
|
||||
);
|
||||
|
||||
return { loading, deleteLoading };
|
||||
return {
|
||||
loading,
|
||||
deleteLoading,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
@@ -254,20 +268,20 @@ const mapDispatchToProps = dispatch => {
|
||||
modifyPermission: (
|
||||
permission: Permission,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
) => {
|
||||
dispatch(modifyPermission(permission, namespace, repoName));
|
||||
},
|
||||
deletePermission: (
|
||||
permission: Permission,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
) => {
|
||||
dispatch(deletePermission(permission, namespace, repoName));
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(translate("repos")(SinglePermission));
|
||||
mapDispatchToProps,
|
||||
)(translate('repos')(SinglePermission));
|
||||
@@ -1,783 +0,0 @@
|
||||
// @flow
|
||||
import configureMockStore from "redux-mock-store";
|
||||
import thunk from "redux-thunk";
|
||||
import fetchMock from "fetch-mock";
|
||||
import reducer, {
|
||||
CREATE_PERMISSION,
|
||||
CREATE_PERMISSION_FAILURE,
|
||||
CREATE_PERMISSION_PENDING,
|
||||
CREATE_PERMISSION_SUCCESS,
|
||||
createPermission,
|
||||
createPermissionSuccess,
|
||||
DELETE_PERMISSION,
|
||||
DELETE_PERMISSION_FAILURE,
|
||||
DELETE_PERMISSION_PENDING,
|
||||
DELETE_PERMISSION_SUCCESS,
|
||||
deletePermission,
|
||||
deletePermissionSuccess,
|
||||
FETCH_PERMISSIONS,
|
||||
FETCH_PERMISSIONS_FAILURE,
|
||||
FETCH_PERMISSIONS_PENDING,
|
||||
FETCH_PERMISSIONS_SUCCESS,
|
||||
fetchPermissions,
|
||||
fetchPermissionsSuccess,
|
||||
getCreatePermissionFailure,
|
||||
getDeletePermissionFailure,
|
||||
getDeletePermissionsFailure,
|
||||
getFetchPermissionsFailure,
|
||||
getModifyPermissionFailure,
|
||||
getModifyPermissionsFailure,
|
||||
getPermissionsOfRepo,
|
||||
hasCreatePermission,
|
||||
isCreatePermissionPending,
|
||||
isDeletePermissionPending,
|
||||
isFetchPermissionsPending,
|
||||
isModifyPermissionPending,
|
||||
MODIFY_PERMISSION,
|
||||
MODIFY_PERMISSION_FAILURE,
|
||||
MODIFY_PERMISSION_PENDING,
|
||||
MODIFY_PERMISSION_SUCCESS,
|
||||
modifyPermission,
|
||||
modifyPermissionSuccess
|
||||
} from "./permissions";
|
||||
import type {Permission, PermissionCollection} from "@scm-manager/ui-types";
|
||||
|
||||
const hitchhiker_puzzle42Permission_user_eins: Permission = {
|
||||
name: "user_eins",
|
||||
type: "READ",
|
||||
groupPermission: false,
|
||||
_links: {
|
||||
self: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_eins"
|
||||
},
|
||||
delete: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_eins"
|
||||
},
|
||||
update: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_eins"
|
||||
}
|
||||
},
|
||||
verbs: []
|
||||
};
|
||||
|
||||
const hitchhiker_puzzle42Permission_user_zwei: Permission = {
|
||||
name: "user_zwei",
|
||||
type: "WRITE",
|
||||
groupPermission: true,
|
||||
_links: {
|
||||
self: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_zwei"
|
||||
},
|
||||
delete: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_zwei"
|
||||
},
|
||||
update: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_zwei"
|
||||
}
|
||||
},
|
||||
verbs: []
|
||||
};
|
||||
|
||||
const hitchhiker_puzzle42Permissions: PermissionCollection = [
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
hitchhiker_puzzle42Permission_user_zwei
|
||||
];
|
||||
|
||||
const hitchhiker_puzzle42RepoPermissions = {
|
||||
_embedded: {
|
||||
permissions: hitchhiker_puzzle42Permissions
|
||||
},
|
||||
_links: {
|
||||
create: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
describe("permission fetch", () => {
|
||||
const REPOS_URL = "/api/v2/repositories";
|
||||
const URL = "repositories";
|
||||
const mockStore = configureMockStore([thunk]);
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it("should successfully fetch permissions to repo hitchhiker/puzzle42", () => {
|
||||
fetchMock.getOnce(
|
||||
REPOS_URL + "/hitchhiker/puzzle42/permissions",
|
||||
hitchhiker_puzzle42RepoPermissions
|
||||
);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_PERMISSIONS_PENDING,
|
||||
payload: {
|
||||
namespace: "hitchhiker",
|
||||
repoName: "puzzle42"
|
||||
},
|
||||
itemId: "hitchhiker/puzzle42"
|
||||
},
|
||||
{
|
||||
type: FETCH_PERMISSIONS_SUCCESS,
|
||||
payload: hitchhiker_puzzle42RepoPermissions,
|
||||
itemId: "hitchhiker/puzzle42"
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(
|
||||
fetchPermissions(
|
||||
URL + "/hitchhiker/puzzle42/permissions",
|
||||
"hitchhiker",
|
||||
"puzzle42"
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch FETCH_PERMISSIONS_FAILURE, it the request fails", () => {
|
||||
fetchMock.getOnce(REPOS_URL + "/hitchhiker/puzzle42/permissions", {
|
||||
status: 500
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(
|
||||
fetchPermissions(
|
||||
URL + "/hitchhiker/puzzle42/permissions",
|
||||
"hitchhiker",
|
||||
"puzzle42"
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(FETCH_PERMISSIONS_PENDING);
|
||||
expect(actions[1].type).toEqual(FETCH_PERMISSIONS_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("should successfully modify user_eins permission", () => {
|
||||
fetchMock.putOnce(
|
||||
hitchhiker_puzzle42Permission_user_eins._links.update.href,
|
||||
{
|
||||
status: 204
|
||||
}
|
||||
);
|
||||
|
||||
let editedPermission = { ...hitchhiker_puzzle42Permission_user_eins, type: "OWNER" };
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store
|
||||
.dispatch(modifyPermission(editedPermission, "hitchhiker", "puzzle42"))
|
||||
.then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(MODIFY_PERMISSION_PENDING);
|
||||
expect(actions[1].type).toEqual(MODIFY_PERMISSION_SUCCESS);
|
||||
});
|
||||
});
|
||||
|
||||
it("should successfully modify user_eins permission and call the callback", () => {
|
||||
fetchMock.putOnce(
|
||||
hitchhiker_puzzle42Permission_user_eins._links.update.href,
|
||||
{
|
||||
status: 204
|
||||
}
|
||||
);
|
||||
|
||||
let editedPermission = { ...hitchhiker_puzzle42Permission_user_eins, type: "OWNER" };
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
let called = false;
|
||||
const callback = () => {
|
||||
called = true;
|
||||
};
|
||||
|
||||
return store
|
||||
.dispatch(
|
||||
modifyPermission(editedPermission, "hitchhiker", "puzzle42", callback)
|
||||
)
|
||||
.then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(MODIFY_PERMISSION_PENDING);
|
||||
expect(actions[1].type).toEqual(MODIFY_PERMISSION_SUCCESS);
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail modifying on HTTP 500", () => {
|
||||
fetchMock.putOnce(
|
||||
hitchhiker_puzzle42Permission_user_eins._links.update.href,
|
||||
{
|
||||
status: 500
|
||||
}
|
||||
);
|
||||
|
||||
let editedPermission = { ...hitchhiker_puzzle42Permission_user_eins, type: "OWNER" };
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store
|
||||
.dispatch(modifyPermission(editedPermission, "hitchhiker", "puzzle42"))
|
||||
.then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(MODIFY_PERMISSION_PENDING);
|
||||
expect(actions[1].type).toEqual(MODIFY_PERMISSION_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("should add a permission successfully", () => {
|
||||
// unmatched
|
||||
fetchMock.postOnce(REPOS_URL + "/hitchhiker/puzzle42/permissions", {
|
||||
status: 204,
|
||||
headers: {
|
||||
location: "repositories/hitchhiker/puzzle42/permissions/user_eins"
|
||||
}
|
||||
});
|
||||
|
||||
fetchMock.getOnce(
|
||||
REPOS_URL + "/hitchhiker/puzzle42/permissions/user_eins",
|
||||
hitchhiker_puzzle42Permission_user_eins
|
||||
);
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(
|
||||
createPermission(
|
||||
URL + "/hitchhiker/puzzle42/permissions",
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
"hitchhiker",
|
||||
"puzzle42"
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(CREATE_PERMISSION_PENDING);
|
||||
expect(actions[1].type).toEqual(CREATE_PERMISSION_SUCCESS);
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail adding a permission on HTTP 500", () => {
|
||||
fetchMock.postOnce(REPOS_URL + "/hitchhiker/puzzle42/permissions", {
|
||||
status: 500
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(
|
||||
createPermission(
|
||||
URL + "/hitchhiker/puzzle42/permissions",
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
"hitchhiker",
|
||||
"puzzle42"
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(CREATE_PERMISSION_PENDING);
|
||||
expect(actions[1].type).toEqual(CREATE_PERMISSION_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("should call the callback after permission successfully created", () => {
|
||||
// unmatched
|
||||
fetchMock.postOnce(REPOS_URL + "/hitchhiker/puzzle42/permissions", {
|
||||
status: 204,
|
||||
headers: {
|
||||
location: "repositories/hitchhiker/puzzle42/permissions/user_eins"
|
||||
}
|
||||
});
|
||||
|
||||
fetchMock.getOnce(
|
||||
REPOS_URL + "/hitchhiker/puzzle42/permissions/user_eins",
|
||||
hitchhiker_puzzle42Permission_user_eins
|
||||
);
|
||||
let callMe = "not yet";
|
||||
|
||||
const callback = () => {
|
||||
callMe = "yeah";
|
||||
};
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(
|
||||
createPermission(
|
||||
URL + "/hitchhiker/puzzle42/permissions",
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
"hitchhiker",
|
||||
"puzzle42",
|
||||
callback
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
expect(callMe).toBe("yeah");
|
||||
});
|
||||
});
|
||||
it("should delete successfully permission user_eins", () => {
|
||||
fetchMock.deleteOnce(
|
||||
hitchhiker_puzzle42Permission_user_eins._links.delete.href,
|
||||
{
|
||||
status: 204
|
||||
}
|
||||
);
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(
|
||||
deletePermission(
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
"hitchhiker",
|
||||
"puzzle42"
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions.length).toBe(2);
|
||||
expect(actions[0].type).toEqual(DELETE_PERMISSION_PENDING);
|
||||
expect(actions[0].payload).toBe(
|
||||
hitchhiker_puzzle42Permission_user_eins
|
||||
);
|
||||
expect(actions[1].type).toEqual(DELETE_PERMISSION_SUCCESS);
|
||||
});
|
||||
});
|
||||
|
||||
it("should call the callback, after successful delete", () => {
|
||||
fetchMock.deleteOnce(
|
||||
hitchhiker_puzzle42Permission_user_eins._links.delete.href,
|
||||
{
|
||||
status: 204
|
||||
}
|
||||
);
|
||||
|
||||
let called = false;
|
||||
const callMe = () => {
|
||||
called = true;
|
||||
};
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(
|
||||
deletePermission(
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
"hitchhiker",
|
||||
"puzzle42",
|
||||
callMe
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
expect(called).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail to delete permission", () => {
|
||||
fetchMock.deleteOnce(
|
||||
hitchhiker_puzzle42Permission_user_eins._links.delete.href,
|
||||
{
|
||||
status: 500
|
||||
}
|
||||
);
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(
|
||||
deletePermission(
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
"hitchhiker",
|
||||
"puzzle42"
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(DELETE_PERMISSION_PENDING);
|
||||
expect(actions[0].payload).toBe(
|
||||
hitchhiker_puzzle42Permission_user_eins
|
||||
);
|
||||
expect(actions[1].type).toEqual(DELETE_PERMISSION_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("permissions 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 permissions on FETCH_PERMISSION_SUCCESS", () => {
|
||||
const newState = reducer(
|
||||
{},
|
||||
fetchPermissionsSuccess(
|
||||
hitchhiker_puzzle42RepoPermissions,
|
||||
"hitchhiker",
|
||||
"puzzle42"
|
||||
)
|
||||
);
|
||||
|
||||
expect(newState["hitchhiker/puzzle42"].entries).toBe(
|
||||
hitchhiker_puzzle42Permissions
|
||||
);
|
||||
});
|
||||
|
||||
it("should update permission", () => {
|
||||
const oldState = {
|
||||
"hitchhiker/puzzle42": {
|
||||
entries: [hitchhiker_puzzle42Permission_user_eins]
|
||||
}
|
||||
};
|
||||
let permissionEdited = { ...hitchhiker_puzzle42Permission_user_eins, type: "OWNER" };
|
||||
let expectedState = {
|
||||
"hitchhiker/puzzle42": {
|
||||
entries: [permissionEdited]
|
||||
}
|
||||
};
|
||||
const newState = reducer(
|
||||
oldState,
|
||||
modifyPermissionSuccess(permissionEdited, "hitchhiker", "puzzle42")
|
||||
);
|
||||
expect(newState["hitchhiker/puzzle42"]).toEqual(
|
||||
expectedState["hitchhiker/puzzle42"]
|
||||
);
|
||||
});
|
||||
|
||||
it("should remove permission from state when delete succeeds", () => {
|
||||
const state = {
|
||||
"hitchhiker/puzzle42": {
|
||||
entries: [
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
hitchhiker_puzzle42Permission_user_zwei
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const expectedState = {
|
||||
"hitchhiker/puzzle42": {
|
||||
entries: [hitchhiker_puzzle42Permission_user_zwei]
|
||||
}
|
||||
};
|
||||
|
||||
const newState = reducer(
|
||||
state,
|
||||
deletePermissionSuccess(
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
"hitchhiker",
|
||||
"puzzle42"
|
||||
)
|
||||
);
|
||||
expect(newState["hitchhiker/puzzle42"]).toEqual(
|
||||
expectedState["hitchhiker/puzzle42"]
|
||||
);
|
||||
});
|
||||
|
||||
it("should add permission", () => {
|
||||
//changing state had to be removed because of errors
|
||||
const oldState = {
|
||||
"hitchhiker/puzzle42": {
|
||||
entries: [hitchhiker_puzzle42Permission_user_eins]
|
||||
}
|
||||
};
|
||||
let expectedState = {
|
||||
"hitchhiker/puzzle42": {
|
||||
entries: [
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
hitchhiker_puzzle42Permission_user_zwei
|
||||
]
|
||||
}
|
||||
};
|
||||
const newState = reducer(
|
||||
oldState,
|
||||
createPermissionSuccess(
|
||||
hitchhiker_puzzle42Permission_user_zwei,
|
||||
"hitchhiker",
|
||||
"puzzle42"
|
||||
)
|
||||
);
|
||||
expect(newState["hitchhiker/puzzle42"]).toEqual(
|
||||
expectedState["hitchhiker/puzzle42"]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("permissions selectors", () => {
|
||||
const error = new Error("something goes wrong");
|
||||
|
||||
it("should return the permissions of one repository", () => {
|
||||
const state = {
|
||||
permissions: {
|
||||
"hitchhiker/puzzle42": {
|
||||
entries: hitchhiker_puzzle42Permissions
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const repoPermissions = getPermissionsOfRepo(
|
||||
state,
|
||||
"hitchhiker",
|
||||
"puzzle42"
|
||||
);
|
||||
expect(repoPermissions).toEqual(hitchhiker_puzzle42Permissions);
|
||||
});
|
||||
|
||||
it("should return true, when fetch permissions is pending", () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[FETCH_PERMISSIONS + "/hitchhiker/puzzle42"]: true
|
||||
}
|
||||
};
|
||||
expect(isFetchPermissionsPending(state, "hitchhiker", "puzzle42")).toEqual(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it("should return false, when fetch permissions is not pending", () => {
|
||||
expect(isFetchPermissionsPending({}, "hitchiker", "puzzle42")).toEqual(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it("should return error when fetch permissions did fail", () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[FETCH_PERMISSIONS + "/hitchhiker/puzzle42"]: error
|
||||
}
|
||||
};
|
||||
expect(getFetchPermissionsFailure(state, "hitchhiker", "puzzle42")).toEqual(
|
||||
error
|
||||
);
|
||||
});
|
||||
|
||||
it("should return undefined when fetch permissions did not fail", () => {
|
||||
expect(getFetchPermissionsFailure({}, "hitchhiker", "puzzle42")).toBe(
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
it("should return true, when modify permission is pending", () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[MODIFY_PERMISSION + "/hitchhiker/puzzle42/user_eins"]: true
|
||||
}
|
||||
};
|
||||
expect(
|
||||
isModifyPermissionPending(
|
||||
state,
|
||||
"hitchhiker",
|
||||
"puzzle42",
|
||||
hitchhiker_puzzle42Permission_user_eins
|
||||
)
|
||||
).toEqual(true);
|
||||
});
|
||||
|
||||
it("should return false, when modify permission is not pending", () => {
|
||||
expect(
|
||||
isModifyPermissionPending(
|
||||
{},
|
||||
"hitchiker",
|
||||
"puzzle42",
|
||||
hitchhiker_puzzle42Permission_user_eins
|
||||
)
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return error when modify permission did fail", () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[MODIFY_PERMISSION + "/hitchhiker/puzzle42/user_eins"]: error
|
||||
}
|
||||
};
|
||||
expect(
|
||||
getModifyPermissionFailure(
|
||||
state,
|
||||
"hitchhiker",
|
||||
"puzzle42",
|
||||
hitchhiker_puzzle42Permission_user_eins
|
||||
)
|
||||
).toEqual(error);
|
||||
});
|
||||
|
||||
it("should return undefined when modify permission did not fail", () => {
|
||||
expect(
|
||||
getModifyPermissionFailure(
|
||||
{},
|
||||
"hitchhiker",
|
||||
"puzzle42",
|
||||
hitchhiker_puzzle42Permission_user_eins
|
||||
)
|
||||
).toBe(undefined);
|
||||
});
|
||||
|
||||
it("should return error when one of the modify permissions did fail", () => {
|
||||
const state = {
|
||||
permissions: {
|
||||
"hitchhiker/puzzle42": { entries: hitchhiker_puzzle42Permissions }
|
||||
},
|
||||
failure: {
|
||||
[MODIFY_PERMISSION + "/hitchhiker/puzzle42/user_eins"]: error
|
||||
}
|
||||
};
|
||||
expect(
|
||||
getModifyPermissionsFailure(state, "hitchhiker", "puzzle42")
|
||||
).toEqual(error);
|
||||
});
|
||||
|
||||
it("should return undefined when no modify permissions did not fail", () => {
|
||||
expect(getModifyPermissionsFailure({}, "hitchhiker", "puzzle42")).toBe(
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
it("should return true, when createPermission is true", () => {
|
||||
const state = {
|
||||
permissions: {
|
||||
"hitchhiker/puzzle42": {
|
||||
createPermission: true
|
||||
}
|
||||
}
|
||||
};
|
||||
expect(hasCreatePermission(state, "hitchhiker", "puzzle42")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false, when createPermission is false", () => {
|
||||
const state = {
|
||||
permissions: {
|
||||
"hitchhiker/puzzle42": {
|
||||
createPermission: false
|
||||
}
|
||||
}
|
||||
};
|
||||
expect(hasCreatePermission(state, "hitchhiker", "puzzle42")).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return true, when delete permission is pending", () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[DELETE_PERMISSION + "/hitchhiker/puzzle42/user_eins"]: true
|
||||
}
|
||||
};
|
||||
expect(
|
||||
isDeletePermissionPending(
|
||||
state,
|
||||
"hitchhiker",
|
||||
"puzzle42",
|
||||
hitchhiker_puzzle42Permission_user_eins
|
||||
)
|
||||
).toEqual(true);
|
||||
});
|
||||
|
||||
it("should return false, when delete permission is not pending", () => {
|
||||
expect(
|
||||
isDeletePermissionPending(
|
||||
{},
|
||||
"hitchiker",
|
||||
"puzzle42",
|
||||
hitchhiker_puzzle42Permission_user_eins
|
||||
)
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return error when delete permission did fail", () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[DELETE_PERMISSION + "/hitchhiker/puzzle42/user_eins"]: error
|
||||
}
|
||||
};
|
||||
expect(
|
||||
getDeletePermissionFailure(
|
||||
state,
|
||||
"hitchhiker",
|
||||
"puzzle42",
|
||||
hitchhiker_puzzle42Permission_user_eins
|
||||
)
|
||||
).toEqual(error);
|
||||
});
|
||||
|
||||
it("should return undefined when delete permission did not fail", () => {
|
||||
expect(
|
||||
getDeletePermissionFailure(
|
||||
{},
|
||||
"hitchhiker",
|
||||
"puzzle42",
|
||||
hitchhiker_puzzle42Permission_user_eins
|
||||
)
|
||||
).toBe(undefined);
|
||||
});
|
||||
|
||||
it("should return error when one of the delete permissions did fail", () => {
|
||||
const state = {
|
||||
permissions: {
|
||||
"hitchhiker/puzzle42": { entries: hitchhiker_puzzle42Permissions }
|
||||
},
|
||||
failure: {
|
||||
[DELETE_PERMISSION + "/hitchhiker/puzzle42/user_eins"]: error
|
||||
}
|
||||
};
|
||||
expect(
|
||||
getDeletePermissionsFailure(state, "hitchhiker", "puzzle42")
|
||||
).toEqual(error);
|
||||
});
|
||||
|
||||
it("should return undefined when no delete permissions did not fail", () => {
|
||||
expect(getDeletePermissionsFailure({}, "hitchhiker", "puzzle42")).toBe(
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
it("should return true, when create permission is pending", () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[CREATE_PERMISSION + "/hitchhiker/puzzle42"]: true
|
||||
}
|
||||
};
|
||||
expect(isCreatePermissionPending(state, "hitchhiker", "puzzle42")).toEqual(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it("should return false, when create permissions is not pending", () => {
|
||||
expect(isCreatePermissionPending({}, "hitchiker", "puzzle42")).toEqual(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it("should return error when create permissions did fail", () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[CREATE_PERMISSION + "/hitchhiker/puzzle42"]: error
|
||||
}
|
||||
};
|
||||
expect(getCreatePermissionFailure(state, "hitchhiker", "puzzle42")).toEqual(
|
||||
error
|
||||
);
|
||||
});
|
||||
|
||||
it("should return undefined when create permissions did not fail", () => {
|
||||
expect(getCreatePermissionFailure({}, "hitchhiker", "puzzle42")).toBe(
|
||||
undefined
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,806 @@
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import reducer, {
|
||||
CREATE_PERMISSION,
|
||||
CREATE_PERMISSION_FAILURE,
|
||||
CREATE_PERMISSION_PENDING,
|
||||
CREATE_PERMISSION_SUCCESS,
|
||||
createPermission,
|
||||
createPermissionSuccess,
|
||||
DELETE_PERMISSION,
|
||||
DELETE_PERMISSION_FAILURE,
|
||||
DELETE_PERMISSION_PENDING,
|
||||
DELETE_PERMISSION_SUCCESS,
|
||||
deletePermission,
|
||||
deletePermissionSuccess,
|
||||
FETCH_PERMISSIONS,
|
||||
FETCH_PERMISSIONS_FAILURE,
|
||||
FETCH_PERMISSIONS_PENDING,
|
||||
FETCH_PERMISSIONS_SUCCESS,
|
||||
fetchPermissions,
|
||||
fetchPermissionsSuccess,
|
||||
getCreatePermissionFailure,
|
||||
getDeletePermissionFailure,
|
||||
getDeletePermissionsFailure,
|
||||
getFetchPermissionsFailure,
|
||||
getModifyPermissionFailure,
|
||||
getModifyPermissionsFailure,
|
||||
getPermissionsOfRepo,
|
||||
hasCreatePermission,
|
||||
isCreatePermissionPending,
|
||||
isDeletePermissionPending,
|
||||
isFetchPermissionsPending,
|
||||
isModifyPermissionPending,
|
||||
MODIFY_PERMISSION,
|
||||
MODIFY_PERMISSION_FAILURE,
|
||||
MODIFY_PERMISSION_PENDING,
|
||||
MODIFY_PERMISSION_SUCCESS,
|
||||
modifyPermission,
|
||||
modifyPermissionSuccess,
|
||||
} from './permissions';
|
||||
import { Permission, PermissionCollection } from '@scm-manager/ui-types';
|
||||
|
||||
const hitchhiker_puzzle42Permission_user_eins: Permission = {
|
||||
name: 'user_eins',
|
||||
type: 'READ',
|
||||
groupPermission: false,
|
||||
_links: {
|
||||
self: {
|
||||
href:
|
||||
'http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_eins',
|
||||
},
|
||||
delete: {
|
||||
href:
|
||||
'http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_eins',
|
||||
},
|
||||
update: {
|
||||
href:
|
||||
'http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_eins',
|
||||
},
|
||||
},
|
||||
verbs: [],
|
||||
};
|
||||
|
||||
const hitchhiker_puzzle42Permission_user_zwei: Permission = {
|
||||
name: 'user_zwei',
|
||||
type: 'WRITE',
|
||||
groupPermission: true,
|
||||
_links: {
|
||||
self: {
|
||||
href:
|
||||
'http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_zwei',
|
||||
},
|
||||
delete: {
|
||||
href:
|
||||
'http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_zwei',
|
||||
},
|
||||
update: {
|
||||
href:
|
||||
'http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_zwei',
|
||||
},
|
||||
},
|
||||
verbs: [],
|
||||
};
|
||||
|
||||
const hitchhiker_puzzle42Permissions: PermissionCollection = [
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
hitchhiker_puzzle42Permission_user_zwei,
|
||||
];
|
||||
|
||||
const hitchhiker_puzzle42RepoPermissions = {
|
||||
_embedded: {
|
||||
permissions: hitchhiker_puzzle42Permissions,
|
||||
},
|
||||
_links: {
|
||||
create: {
|
||||
href:
|
||||
'http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('permission fetch', () => {
|
||||
const REPOS_URL = '/api/v2/repositories';
|
||||
const URL = 'repositories';
|
||||
const mockStore = configureMockStore([thunk]);
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it('should successfully fetch permissions to repo hitchhiker/puzzle42', () => {
|
||||
fetchMock.getOnce(
|
||||
REPOS_URL + '/hitchhiker/puzzle42/permissions',
|
||||
hitchhiker_puzzle42RepoPermissions,
|
||||
);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_PERMISSIONS_PENDING,
|
||||
payload: {
|
||||
namespace: 'hitchhiker',
|
||||
repoName: 'puzzle42',
|
||||
},
|
||||
itemId: 'hitchhiker/puzzle42',
|
||||
},
|
||||
{
|
||||
type: FETCH_PERMISSIONS_SUCCESS,
|
||||
payload: hitchhiker_puzzle42RepoPermissions,
|
||||
itemId: 'hitchhiker/puzzle42',
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(
|
||||
fetchPermissions(
|
||||
URL + '/hitchhiker/puzzle42/permissions',
|
||||
'hitchhiker',
|
||||
'puzzle42',
|
||||
),
|
||||
)
|
||||
.then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch FETCH_PERMISSIONS_FAILURE, it the request fails', () => {
|
||||
fetchMock.getOnce(REPOS_URL + '/hitchhiker/puzzle42/permissions', {
|
||||
status: 500,
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(
|
||||
fetchPermissions(
|
||||
URL + '/hitchhiker/puzzle42/permissions',
|
||||
'hitchhiker',
|
||||
'puzzle42',
|
||||
),
|
||||
)
|
||||
.then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(FETCH_PERMISSIONS_PENDING);
|
||||
expect(actions[1].type).toEqual(FETCH_PERMISSIONS_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully modify user_eins permission', () => {
|
||||
fetchMock.putOnce(
|
||||
hitchhiker_puzzle42Permission_user_eins._links.update.href,
|
||||
{
|
||||
status: 204,
|
||||
},
|
||||
);
|
||||
|
||||
let editedPermission = {
|
||||
...hitchhiker_puzzle42Permission_user_eins,
|
||||
type: 'OWNER',
|
||||
};
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store
|
||||
.dispatch(modifyPermission(editedPermission, 'hitchhiker', 'puzzle42'))
|
||||
.then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(MODIFY_PERMISSION_PENDING);
|
||||
expect(actions[1].type).toEqual(MODIFY_PERMISSION_SUCCESS);
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully modify user_eins permission and call the callback', () => {
|
||||
fetchMock.putOnce(
|
||||
hitchhiker_puzzle42Permission_user_eins._links.update.href,
|
||||
{
|
||||
status: 204,
|
||||
},
|
||||
);
|
||||
|
||||
let editedPermission = {
|
||||
...hitchhiker_puzzle42Permission_user_eins,
|
||||
type: 'OWNER',
|
||||
};
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
let called = false;
|
||||
const callback = () => {
|
||||
called = true;
|
||||
};
|
||||
|
||||
return store
|
||||
.dispatch(
|
||||
modifyPermission(editedPermission, 'hitchhiker', 'puzzle42', callback),
|
||||
)
|
||||
.then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(MODIFY_PERMISSION_PENDING);
|
||||
expect(actions[1].type).toEqual(MODIFY_PERMISSION_SUCCESS);
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail modifying on HTTP 500', () => {
|
||||
fetchMock.putOnce(
|
||||
hitchhiker_puzzle42Permission_user_eins._links.update.href,
|
||||
{
|
||||
status: 500,
|
||||
},
|
||||
);
|
||||
|
||||
let editedPermission = {
|
||||
...hitchhiker_puzzle42Permission_user_eins,
|
||||
type: 'OWNER',
|
||||
};
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store
|
||||
.dispatch(modifyPermission(editedPermission, 'hitchhiker', 'puzzle42'))
|
||||
.then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(MODIFY_PERMISSION_PENDING);
|
||||
expect(actions[1].type).toEqual(MODIFY_PERMISSION_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a permission successfully', () => {
|
||||
// unmatched
|
||||
fetchMock.postOnce(REPOS_URL + '/hitchhiker/puzzle42/permissions', {
|
||||
status: 204,
|
||||
headers: {
|
||||
location: 'repositories/hitchhiker/puzzle42/permissions/user_eins',
|
||||
},
|
||||
});
|
||||
|
||||
fetchMock.getOnce(
|
||||
REPOS_URL + '/hitchhiker/puzzle42/permissions/user_eins',
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
);
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(
|
||||
createPermission(
|
||||
URL + '/hitchhiker/puzzle42/permissions',
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
'hitchhiker',
|
||||
'puzzle42',
|
||||
),
|
||||
)
|
||||
.then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(CREATE_PERMISSION_PENDING);
|
||||
expect(actions[1].type).toEqual(CREATE_PERMISSION_SUCCESS);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail adding a permission on HTTP 500', () => {
|
||||
fetchMock.postOnce(REPOS_URL + '/hitchhiker/puzzle42/permissions', {
|
||||
status: 500,
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(
|
||||
createPermission(
|
||||
URL + '/hitchhiker/puzzle42/permissions',
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
'hitchhiker',
|
||||
'puzzle42',
|
||||
),
|
||||
)
|
||||
.then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(CREATE_PERMISSION_PENDING);
|
||||
expect(actions[1].type).toEqual(CREATE_PERMISSION_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should call the callback after permission successfully created', () => {
|
||||
// unmatched
|
||||
fetchMock.postOnce(REPOS_URL + '/hitchhiker/puzzle42/permissions', {
|
||||
status: 204,
|
||||
headers: {
|
||||
location: 'repositories/hitchhiker/puzzle42/permissions/user_eins',
|
||||
},
|
||||
});
|
||||
|
||||
fetchMock.getOnce(
|
||||
REPOS_URL + '/hitchhiker/puzzle42/permissions/user_eins',
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
);
|
||||
let callMe = 'not yet';
|
||||
|
||||
const callback = () => {
|
||||
callMe = 'yeah';
|
||||
};
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(
|
||||
createPermission(
|
||||
URL + '/hitchhiker/puzzle42/permissions',
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
'hitchhiker',
|
||||
'puzzle42',
|
||||
callback,
|
||||
),
|
||||
)
|
||||
.then(() => {
|
||||
expect(callMe).toBe('yeah');
|
||||
});
|
||||
});
|
||||
it('should delete successfully permission user_eins', () => {
|
||||
fetchMock.deleteOnce(
|
||||
hitchhiker_puzzle42Permission_user_eins._links.delete.href,
|
||||
{
|
||||
status: 204,
|
||||
},
|
||||
);
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(
|
||||
deletePermission(
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
'hitchhiker',
|
||||
'puzzle42',
|
||||
),
|
||||
)
|
||||
.then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions.length).toBe(2);
|
||||
expect(actions[0].type).toEqual(DELETE_PERMISSION_PENDING);
|
||||
expect(actions[0].payload).toBe(
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
);
|
||||
expect(actions[1].type).toEqual(DELETE_PERMISSION_SUCCESS);
|
||||
});
|
||||
});
|
||||
|
||||
it('should call the callback, after successful delete', () => {
|
||||
fetchMock.deleteOnce(
|
||||
hitchhiker_puzzle42Permission_user_eins._links.delete.href,
|
||||
{
|
||||
status: 204,
|
||||
},
|
||||
);
|
||||
|
||||
let called = false;
|
||||
const callMe = () => {
|
||||
called = true;
|
||||
};
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(
|
||||
deletePermission(
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
'hitchhiker',
|
||||
'puzzle42',
|
||||
callMe,
|
||||
),
|
||||
)
|
||||
.then(() => {
|
||||
expect(called).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail to delete permission', () => {
|
||||
fetchMock.deleteOnce(
|
||||
hitchhiker_puzzle42Permission_user_eins._links.delete.href,
|
||||
{
|
||||
status: 500,
|
||||
},
|
||||
);
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(
|
||||
deletePermission(
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
'hitchhiker',
|
||||
'puzzle42',
|
||||
),
|
||||
)
|
||||
.then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(DELETE_PERMISSION_PENDING);
|
||||
expect(actions[0].payload).toBe(
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
);
|
||||
expect(actions[1].type).toEqual(DELETE_PERMISSION_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('permissions 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 permissions on FETCH_PERMISSION_SUCCESS', () => {
|
||||
const newState = reducer(
|
||||
{},
|
||||
fetchPermissionsSuccess(
|
||||
hitchhiker_puzzle42RepoPermissions,
|
||||
'hitchhiker',
|
||||
'puzzle42',
|
||||
),
|
||||
);
|
||||
|
||||
expect(newState['hitchhiker/puzzle42'].entries).toBe(
|
||||
hitchhiker_puzzle42Permissions,
|
||||
);
|
||||
});
|
||||
|
||||
it('should update permission', () => {
|
||||
const oldState = {
|
||||
'hitchhiker/puzzle42': {
|
||||
entries: [hitchhiker_puzzle42Permission_user_eins],
|
||||
},
|
||||
};
|
||||
let permissionEdited = {
|
||||
...hitchhiker_puzzle42Permission_user_eins,
|
||||
type: 'OWNER',
|
||||
};
|
||||
let expectedState = {
|
||||
'hitchhiker/puzzle42': {
|
||||
entries: [permissionEdited],
|
||||
},
|
||||
};
|
||||
const newState = reducer(
|
||||
oldState,
|
||||
modifyPermissionSuccess(permissionEdited, 'hitchhiker', 'puzzle42'),
|
||||
);
|
||||
expect(newState['hitchhiker/puzzle42']).toEqual(
|
||||
expectedState['hitchhiker/puzzle42'],
|
||||
);
|
||||
});
|
||||
|
||||
it('should remove permission from state when delete succeeds', () => {
|
||||
const state = {
|
||||
'hitchhiker/puzzle42': {
|
||||
entries: [
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
hitchhiker_puzzle42Permission_user_zwei,
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const expectedState = {
|
||||
'hitchhiker/puzzle42': {
|
||||
entries: [hitchhiker_puzzle42Permission_user_zwei],
|
||||
},
|
||||
};
|
||||
|
||||
const newState = reducer(
|
||||
state,
|
||||
deletePermissionSuccess(
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
'hitchhiker',
|
||||
'puzzle42',
|
||||
),
|
||||
);
|
||||
expect(newState['hitchhiker/puzzle42']).toEqual(
|
||||
expectedState['hitchhiker/puzzle42'],
|
||||
);
|
||||
});
|
||||
|
||||
it('should add permission', () => {
|
||||
//changing state had to be removed because of errors
|
||||
const oldState = {
|
||||
'hitchhiker/puzzle42': {
|
||||
entries: [hitchhiker_puzzle42Permission_user_eins],
|
||||
},
|
||||
};
|
||||
let expectedState = {
|
||||
'hitchhiker/puzzle42': {
|
||||
entries: [
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
hitchhiker_puzzle42Permission_user_zwei,
|
||||
],
|
||||
},
|
||||
};
|
||||
const newState = reducer(
|
||||
oldState,
|
||||
createPermissionSuccess(
|
||||
hitchhiker_puzzle42Permission_user_zwei,
|
||||
'hitchhiker',
|
||||
'puzzle42',
|
||||
),
|
||||
);
|
||||
expect(newState['hitchhiker/puzzle42']).toEqual(
|
||||
expectedState['hitchhiker/puzzle42'],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('permissions selectors', () => {
|
||||
const error = new Error('something goes wrong');
|
||||
|
||||
it('should return the permissions of one repository', () => {
|
||||
const state = {
|
||||
permissions: {
|
||||
'hitchhiker/puzzle42': {
|
||||
entries: hitchhiker_puzzle42Permissions,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const repoPermissions = getPermissionsOfRepo(
|
||||
state,
|
||||
'hitchhiker',
|
||||
'puzzle42',
|
||||
);
|
||||
expect(repoPermissions).toEqual(hitchhiker_puzzle42Permissions);
|
||||
});
|
||||
|
||||
it('should return true, when fetch permissions is pending', () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[FETCH_PERMISSIONS + '/hitchhiker/puzzle42']: true,
|
||||
},
|
||||
};
|
||||
expect(isFetchPermissionsPending(state, 'hitchhiker', 'puzzle42')).toEqual(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return false, when fetch permissions is not pending', () => {
|
||||
expect(isFetchPermissionsPending({}, 'hitchiker', 'puzzle42')).toEqual(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error when fetch permissions did fail', () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[FETCH_PERMISSIONS + '/hitchhiker/puzzle42']: error,
|
||||
},
|
||||
};
|
||||
expect(getFetchPermissionsFailure(state, 'hitchhiker', 'puzzle42')).toEqual(
|
||||
error,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return undefined when fetch permissions did not fail', () => {
|
||||
expect(getFetchPermissionsFailure({}, 'hitchhiker', 'puzzle42')).toBe(
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return true, when modify permission is pending', () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[MODIFY_PERMISSION + '/hitchhiker/puzzle42/user_eins']: true,
|
||||
},
|
||||
};
|
||||
expect(
|
||||
isModifyPermissionPending(
|
||||
state,
|
||||
'hitchhiker',
|
||||
'puzzle42',
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
),
|
||||
).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false, when modify permission is not pending', () => {
|
||||
expect(
|
||||
isModifyPermissionPending(
|
||||
{},
|
||||
'hitchiker',
|
||||
'puzzle42',
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
),
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return error when modify permission did fail', () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[MODIFY_PERMISSION + '/hitchhiker/puzzle42/user_eins']: error,
|
||||
},
|
||||
};
|
||||
expect(
|
||||
getModifyPermissionFailure(
|
||||
state,
|
||||
'hitchhiker',
|
||||
'puzzle42',
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
),
|
||||
).toEqual(error);
|
||||
});
|
||||
|
||||
it('should return undefined when modify permission did not fail', () => {
|
||||
expect(
|
||||
getModifyPermissionFailure(
|
||||
{},
|
||||
'hitchhiker',
|
||||
'puzzle42',
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
),
|
||||
).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should return error when one of the modify permissions did fail', () => {
|
||||
const state = {
|
||||
permissions: {
|
||||
'hitchhiker/puzzle42': {
|
||||
entries: hitchhiker_puzzle42Permissions,
|
||||
},
|
||||
},
|
||||
failure: {
|
||||
[MODIFY_PERMISSION + '/hitchhiker/puzzle42/user_eins']: error,
|
||||
},
|
||||
};
|
||||
expect(
|
||||
getModifyPermissionsFailure(state, 'hitchhiker', 'puzzle42'),
|
||||
).toEqual(error);
|
||||
});
|
||||
|
||||
it('should return undefined when no modify permissions did not fail', () => {
|
||||
expect(getModifyPermissionsFailure({}, 'hitchhiker', 'puzzle42')).toBe(
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return true, when createPermission is true', () => {
|
||||
const state = {
|
||||
permissions: {
|
||||
'hitchhiker/puzzle42': {
|
||||
createPermission: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(hasCreatePermission(state, 'hitchhiker', 'puzzle42')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false, when createPermission is false', () => {
|
||||
const state = {
|
||||
permissions: {
|
||||
'hitchhiker/puzzle42': {
|
||||
createPermission: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(hasCreatePermission(state, 'hitchhiker', 'puzzle42')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return true, when delete permission is pending', () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[DELETE_PERMISSION + '/hitchhiker/puzzle42/user_eins']: true,
|
||||
},
|
||||
};
|
||||
expect(
|
||||
isDeletePermissionPending(
|
||||
state,
|
||||
'hitchhiker',
|
||||
'puzzle42',
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
),
|
||||
).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false, when delete permission is not pending', () => {
|
||||
expect(
|
||||
isDeletePermissionPending(
|
||||
{},
|
||||
'hitchiker',
|
||||
'puzzle42',
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
),
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return error when delete permission did fail', () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[DELETE_PERMISSION + '/hitchhiker/puzzle42/user_eins']: error,
|
||||
},
|
||||
};
|
||||
expect(
|
||||
getDeletePermissionFailure(
|
||||
state,
|
||||
'hitchhiker',
|
||||
'puzzle42',
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
),
|
||||
).toEqual(error);
|
||||
});
|
||||
|
||||
it('should return undefined when delete permission did not fail', () => {
|
||||
expect(
|
||||
getDeletePermissionFailure(
|
||||
{},
|
||||
'hitchhiker',
|
||||
'puzzle42',
|
||||
hitchhiker_puzzle42Permission_user_eins,
|
||||
),
|
||||
).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should return error when one of the delete permissions did fail', () => {
|
||||
const state = {
|
||||
permissions: {
|
||||
'hitchhiker/puzzle42': {
|
||||
entries: hitchhiker_puzzle42Permissions,
|
||||
},
|
||||
},
|
||||
failure: {
|
||||
[DELETE_PERMISSION + '/hitchhiker/puzzle42/user_eins']: error,
|
||||
},
|
||||
};
|
||||
expect(
|
||||
getDeletePermissionsFailure(state, 'hitchhiker', 'puzzle42'),
|
||||
).toEqual(error);
|
||||
});
|
||||
|
||||
it('should return undefined when no delete permissions did not fail', () => {
|
||||
expect(getDeletePermissionsFailure({}, 'hitchhiker', 'puzzle42')).toBe(
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return true, when create permission is pending', () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[CREATE_PERMISSION + '/hitchhiker/puzzle42']: true,
|
||||
},
|
||||
};
|
||||
expect(isCreatePermissionPending(state, 'hitchhiker', 'puzzle42')).toEqual(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return false, when create permissions is not pending', () => {
|
||||
expect(isCreatePermissionPending({}, 'hitchiker', 'puzzle42')).toEqual(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error when create permissions did fail', () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[CREATE_PERMISSION + '/hitchhiker/puzzle42']: error,
|
||||
},
|
||||
};
|
||||
expect(getCreatePermissionFailure(state, 'hitchhiker', 'puzzle42')).toEqual(
|
||||
error,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return undefined when create permissions did not fail', () => {
|
||||
expect(getCreatePermissionFailure({}, 'hitchhiker', 'puzzle42')).toBe(
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,93 +1,55 @@
|
||||
// @flow
|
||||
|
||||
import type { Action } from "@scm-manager/ui-components";
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
import * as types from "../../../modules/types";
|
||||
import type {
|
||||
import { Action } from '@scm-manager/ui-components';
|
||||
import { apiClient } from '@scm-manager/ui-components';
|
||||
import * as types from '../../../modules/types';
|
||||
import {
|
||||
RepositoryRole,
|
||||
Permission,
|
||||
PermissionCollection,
|
||||
PermissionCreateEntry
|
||||
} from "@scm-manager/ui-types";
|
||||
import { isPending } from "../../../modules/pending";
|
||||
import { getFailure } from "../../../modules/failure";
|
||||
import { Dispatch } from "redux";
|
||||
PermissionCreateEntry,
|
||||
} from '@scm-manager/ui-types';
|
||||
import { isPending } from '../../../modules/pending';
|
||||
import { getFailure } from '../../../modules/failure';
|
||||
import { Dispatch } from 'redux';
|
||||
|
||||
export const FETCH_AVAILABLE = "scm/permissions/FETCH_AVAILABLE";
|
||||
export const FETCH_AVAILABLE_PENDING = `${FETCH_AVAILABLE}_${
|
||||
types.PENDING_SUFFIX
|
||||
}`;
|
||||
export const FETCH_AVAILABLE_SUCCESS = `${FETCH_AVAILABLE}_${
|
||||
types.SUCCESS_SUFFIX
|
||||
}`;
|
||||
export const FETCH_AVAILABLE_FAILURE = `${FETCH_AVAILABLE}_${
|
||||
types.FAILURE_SUFFIX
|
||||
}`;
|
||||
export const FETCH_PERMISSIONS = "scm/permissions/FETCH_PERMISSIONS";
|
||||
export const FETCH_PERMISSIONS_PENDING = `${FETCH_PERMISSIONS}_${
|
||||
types.PENDING_SUFFIX
|
||||
}`;
|
||||
export const FETCH_PERMISSIONS_SUCCESS = `${FETCH_PERMISSIONS}_${
|
||||
types.SUCCESS_SUFFIX
|
||||
}`;
|
||||
export const FETCH_PERMISSIONS_FAILURE = `${FETCH_PERMISSIONS}_${
|
||||
types.FAILURE_SUFFIX
|
||||
}`;
|
||||
export const MODIFY_PERMISSION = "scm/permissions/MODFIY_PERMISSION";
|
||||
export const MODIFY_PERMISSION_PENDING = `${MODIFY_PERMISSION}_${
|
||||
types.PENDING_SUFFIX
|
||||
}`;
|
||||
export const MODIFY_PERMISSION_SUCCESS = `${MODIFY_PERMISSION}_${
|
||||
types.SUCCESS_SUFFIX
|
||||
}`;
|
||||
export const MODIFY_PERMISSION_FAILURE = `${MODIFY_PERMISSION}_${
|
||||
types.FAILURE_SUFFIX
|
||||
}`;
|
||||
export const MODIFY_PERMISSION_RESET = `${MODIFY_PERMISSION}_${
|
||||
types.RESET_SUFFIX
|
||||
}`;
|
||||
export const CREATE_PERMISSION = "scm/permissions/CREATE_PERMISSION";
|
||||
export const CREATE_PERMISSION_PENDING = `${CREATE_PERMISSION}_${
|
||||
types.PENDING_SUFFIX
|
||||
}`;
|
||||
export const CREATE_PERMISSION_SUCCESS = `${CREATE_PERMISSION}_${
|
||||
types.SUCCESS_SUFFIX
|
||||
}`;
|
||||
export const CREATE_PERMISSION_FAILURE = `${CREATE_PERMISSION}_${
|
||||
types.FAILURE_SUFFIX
|
||||
}`;
|
||||
export const CREATE_PERMISSION_RESET = `${CREATE_PERMISSION}_${
|
||||
types.RESET_SUFFIX
|
||||
}`;
|
||||
export const DELETE_PERMISSION = "scm/permissions/DELETE_PERMISSION";
|
||||
export const DELETE_PERMISSION_PENDING = `${DELETE_PERMISSION}_${
|
||||
types.PENDING_SUFFIX
|
||||
}`;
|
||||
export const DELETE_PERMISSION_SUCCESS = `${DELETE_PERMISSION}_${
|
||||
types.SUCCESS_SUFFIX
|
||||
}`;
|
||||
export const DELETE_PERMISSION_FAILURE = `${DELETE_PERMISSION}_${
|
||||
types.FAILURE_SUFFIX
|
||||
}`;
|
||||
export const DELETE_PERMISSION_RESET = `${DELETE_PERMISSION}_${
|
||||
types.RESET_SUFFIX
|
||||
}`;
|
||||
export const FETCH_AVAILABLE = 'scm/permissions/FETCH_AVAILABLE';
|
||||
export const FETCH_AVAILABLE_PENDING = `${FETCH_AVAILABLE}_${types.PENDING_SUFFIX}`;
|
||||
export const FETCH_AVAILABLE_SUCCESS = `${FETCH_AVAILABLE}_${types.SUCCESS_SUFFIX}`;
|
||||
export const FETCH_AVAILABLE_FAILURE = `${FETCH_AVAILABLE}_${types.FAILURE_SUFFIX}`;
|
||||
export const FETCH_PERMISSIONS = 'scm/permissions/FETCH_PERMISSIONS';
|
||||
export const FETCH_PERMISSIONS_PENDING = `${FETCH_PERMISSIONS}_${types.PENDING_SUFFIX}`;
|
||||
export const FETCH_PERMISSIONS_SUCCESS = `${FETCH_PERMISSIONS}_${types.SUCCESS_SUFFIX}`;
|
||||
export const FETCH_PERMISSIONS_FAILURE = `${FETCH_PERMISSIONS}_${types.FAILURE_SUFFIX}`;
|
||||
export const MODIFY_PERMISSION = 'scm/permissions/MODFIY_PERMISSION';
|
||||
export const MODIFY_PERMISSION_PENDING = `${MODIFY_PERMISSION}_${types.PENDING_SUFFIX}`;
|
||||
export const MODIFY_PERMISSION_SUCCESS = `${MODIFY_PERMISSION}_${types.SUCCESS_SUFFIX}`;
|
||||
export const MODIFY_PERMISSION_FAILURE = `${MODIFY_PERMISSION}_${types.FAILURE_SUFFIX}`;
|
||||
export const MODIFY_PERMISSION_RESET = `${MODIFY_PERMISSION}_${types.RESET_SUFFIX}`;
|
||||
export const CREATE_PERMISSION = 'scm/permissions/CREATE_PERMISSION';
|
||||
export const CREATE_PERMISSION_PENDING = `${CREATE_PERMISSION}_${types.PENDING_SUFFIX}`;
|
||||
export const CREATE_PERMISSION_SUCCESS = `${CREATE_PERMISSION}_${types.SUCCESS_SUFFIX}`;
|
||||
export const CREATE_PERMISSION_FAILURE = `${CREATE_PERMISSION}_${types.FAILURE_SUFFIX}`;
|
||||
export const CREATE_PERMISSION_RESET = `${CREATE_PERMISSION}_${types.RESET_SUFFIX}`;
|
||||
export const DELETE_PERMISSION = 'scm/permissions/DELETE_PERMISSION';
|
||||
export const DELETE_PERMISSION_PENDING = `${DELETE_PERMISSION}_${types.PENDING_SUFFIX}`;
|
||||
export const DELETE_PERMISSION_SUCCESS = `${DELETE_PERMISSION}_${types.SUCCESS_SUFFIX}`;
|
||||
export const DELETE_PERMISSION_FAILURE = `${DELETE_PERMISSION}_${types.FAILURE_SUFFIX}`;
|
||||
export const DELETE_PERMISSION_RESET = `${DELETE_PERMISSION}_${types.RESET_SUFFIX}`;
|
||||
|
||||
const CONTENT_TYPE = "application/vnd.scmm-repositoryPermission+json";
|
||||
const CONTENT_TYPE = 'application/vnd.scmm-repositoryPermission+json';
|
||||
|
||||
// fetch available permissions
|
||||
|
||||
export function fetchAvailablePermissionsIfNeeded(
|
||||
repositoryRolesLink: string,
|
||||
repositoryVerbsLink: string
|
||||
repositoryVerbsLink: string,
|
||||
) {
|
||||
return function(dispatch: any, getState: () => Object) {
|
||||
return function(dispatch: any, getState: () => object) {
|
||||
if (shouldFetchAvailablePermissions(getState())) {
|
||||
return fetchAvailablePermissions(
|
||||
dispatch,
|
||||
getState,
|
||||
repositoryRolesLink,
|
||||
repositoryVerbsLink
|
||||
repositoryVerbsLink,
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -95,9 +57,9 @@ export function fetchAvailablePermissionsIfNeeded(
|
||||
|
||||
export function fetchAvailablePermissions(
|
||||
dispatch: any,
|
||||
getState: () => Object,
|
||||
getState: () => object,
|
||||
repositoryRolesLink: string,
|
||||
repositoryVerbsLink: string
|
||||
repositoryVerbsLink: string,
|
||||
) {
|
||||
dispatch(fetchAvailablePending());
|
||||
return apiClient
|
||||
@@ -112,7 +74,7 @@ export function fetchAvailablePermissions(
|
||||
.then(repositoryVerbs => {
|
||||
return {
|
||||
repositoryVerbs,
|
||||
repositoryRoles
|
||||
repositoryRoles,
|
||||
};
|
||||
});
|
||||
})
|
||||
@@ -124,7 +86,7 @@ export function fetchAvailablePermissions(
|
||||
});
|
||||
}
|
||||
|
||||
export function shouldFetchAvailablePermissions(state: Object) {
|
||||
export function shouldFetchAvailablePermissions(state: object) {
|
||||
if (
|
||||
isFetchAvailablePermissionsPending(state) ||
|
||||
getFetchAvailablePermissionsFailure(state)
|
||||
@@ -138,17 +100,17 @@ export function fetchAvailablePending(): Action {
|
||||
return {
|
||||
type: FETCH_AVAILABLE_PENDING,
|
||||
payload: {},
|
||||
itemId: "available"
|
||||
itemId: 'available',
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchAvailableSuccess(
|
||||
available: [RepositoryRole[], string[]]
|
||||
available: [RepositoryRole[], string[]],
|
||||
): Action {
|
||||
return {
|
||||
type: FETCH_AVAILABLE_SUCCESS,
|
||||
payload: available,
|
||||
itemId: "available"
|
||||
itemId: 'available',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -156,9 +118,9 @@ export function fetchAvailableFailure(error: Error): Action {
|
||||
return {
|
||||
type: FETCH_AVAILABLE_FAILURE,
|
||||
payload: {
|
||||
error
|
||||
error,
|
||||
},
|
||||
itemId: "available"
|
||||
itemId: 'available',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -167,7 +129,7 @@ export function fetchAvailableFailure(error: Error): Action {
|
||||
export function fetchPermissions(
|
||||
link: string,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
) {
|
||||
return function(dispatch: any) {
|
||||
dispatch(fetchPermissionsPending(namespace, repoName));
|
||||
@@ -185,43 +147,43 @@ export function fetchPermissions(
|
||||
|
||||
export function fetchPermissionsPending(
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
): Action {
|
||||
return {
|
||||
type: FETCH_PERMISSIONS_PENDING,
|
||||
payload: {
|
||||
namespace,
|
||||
repoName
|
||||
repoName,
|
||||
},
|
||||
itemId: namespace + "/" + repoName
|
||||
itemId: namespace + '/' + repoName,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchPermissionsSuccess(
|
||||
permissions: any,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
): Action {
|
||||
return {
|
||||
type: FETCH_PERMISSIONS_SUCCESS,
|
||||
payload: permissions,
|
||||
itemId: namespace + "/" + repoName
|
||||
itemId: namespace + '/' + repoName,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchPermissionsFailure(
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
error: Error
|
||||
error: Error,
|
||||
): Action {
|
||||
return {
|
||||
type: FETCH_PERMISSIONS_FAILURE,
|
||||
payload: {
|
||||
namespace,
|
||||
repoName,
|
||||
error
|
||||
error,
|
||||
},
|
||||
itemId: namespace + "/" + repoName
|
||||
itemId: namespace + '/' + repoName,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -231,7 +193,7 @@ export function modifyPermission(
|
||||
permission: Permission,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
callback?: () => void
|
||||
callback?: () => void,
|
||||
) {
|
||||
return function(dispatch: any) {
|
||||
dispatch(modifyPermissionPending(permission, namespace, repoName));
|
||||
@@ -252,27 +214,27 @@ export function modifyPermission(
|
||||
export function modifyPermissionPending(
|
||||
permission: Permission,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
): Action {
|
||||
return {
|
||||
type: MODIFY_PERMISSION_PENDING,
|
||||
payload: permission,
|
||||
itemId: createItemId(permission, namespace, repoName)
|
||||
itemId: createItemId(permission, namespace, repoName),
|
||||
};
|
||||
}
|
||||
|
||||
export function modifyPermissionSuccess(
|
||||
permission: Permission,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
): Action {
|
||||
return {
|
||||
type: MODIFY_PERMISSION_SUCCESS,
|
||||
payload: {
|
||||
permission,
|
||||
position: namespace + "/" + repoName
|
||||
position: namespace + '/' + repoName,
|
||||
},
|
||||
itemId: createItemId(permission, namespace, repoName)
|
||||
itemId: createItemId(permission, namespace, repoName),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -280,18 +242,21 @@ export function modifyPermissionFailure(
|
||||
permission: Permission,
|
||||
error: Error,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
): Action {
|
||||
return {
|
||||
type: MODIFY_PERMISSION_FAILURE,
|
||||
payload: { error, permission },
|
||||
itemId: createItemId(permission, namespace, repoName)
|
||||
payload: {
|
||||
error,
|
||||
permission,
|
||||
},
|
||||
itemId: createItemId(permission, namespace, repoName),
|
||||
};
|
||||
}
|
||||
|
||||
function newPermissions(
|
||||
oldPermissions: PermissionCollection,
|
||||
newPermission: Permission
|
||||
newPermission: Permission,
|
||||
) {
|
||||
for (let i = 0; i < oldPermissions.length; i++) {
|
||||
if (oldPermissions[i].name === newPermission.name) {
|
||||
@@ -306,9 +271,9 @@ export function modifyPermissionReset(namespace: string, repoName: string) {
|
||||
type: MODIFY_PERMISSION_RESET,
|
||||
payload: {
|
||||
namespace,
|
||||
repoName
|
||||
repoName,
|
||||
},
|
||||
itemId: namespace + "/" + repoName
|
||||
itemId: namespace + '/' + repoName,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -318,27 +283,27 @@ export function createPermission(
|
||||
permission: PermissionCreateEntry,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
callback?: () => void
|
||||
callback?: () => void,
|
||||
) {
|
||||
return function(dispatch: Dispatch) {
|
||||
dispatch(createPermissionPending(permission, namespace, repoName));
|
||||
return apiClient
|
||||
.post(link, permission, CONTENT_TYPE)
|
||||
.then(response => {
|
||||
const location = response.headers.get("Location");
|
||||
const location = response.headers.get('Location');
|
||||
return apiClient.get(location);
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(createdPermission => {
|
||||
dispatch(
|
||||
createPermissionSuccess(createdPermission, namespace, repoName)
|
||||
createPermissionSuccess(createdPermission, namespace, repoName),
|
||||
);
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
.catch(err =>
|
||||
dispatch(createPermissionFailure(err, namespace, repoName))
|
||||
dispatch(createPermissionFailure(err, namespace, repoName)),
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -346,46 +311,46 @@ export function createPermission(
|
||||
export function createPermissionPending(
|
||||
permission: PermissionCreateEntry,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
): Action {
|
||||
return {
|
||||
type: CREATE_PERMISSION_PENDING,
|
||||
payload: permission,
|
||||
itemId: namespace + "/" + repoName
|
||||
itemId: namespace + '/' + repoName,
|
||||
};
|
||||
}
|
||||
|
||||
export function createPermissionSuccess(
|
||||
permission: PermissionCreateEntry,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
): Action {
|
||||
return {
|
||||
type: CREATE_PERMISSION_SUCCESS,
|
||||
payload: {
|
||||
permission,
|
||||
position: namespace + "/" + repoName
|
||||
position: namespace + '/' + repoName,
|
||||
},
|
||||
itemId: namespace + "/" + repoName
|
||||
itemId: namespace + '/' + repoName,
|
||||
};
|
||||
}
|
||||
|
||||
export function createPermissionFailure(
|
||||
error: Error,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
): Action {
|
||||
return {
|
||||
type: CREATE_PERMISSION_FAILURE,
|
||||
payload: error,
|
||||
itemId: namespace + "/" + repoName
|
||||
itemId: namespace + '/' + repoName,
|
||||
};
|
||||
}
|
||||
|
||||
export function createPermissionReset(namespace: string, repoName: string) {
|
||||
return {
|
||||
type: CREATE_PERMISSION_RESET,
|
||||
itemId: namespace + "/" + repoName
|
||||
itemId: namespace + '/' + repoName,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -395,7 +360,7 @@ export function deletePermission(
|
||||
permission: Permission,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
callback?: () => void
|
||||
callback?: () => void,
|
||||
) {
|
||||
return function(dispatch: any) {
|
||||
dispatch(deletePermissionPending(permission, namespace, repoName));
|
||||
@@ -416,27 +381,27 @@ export function deletePermission(
|
||||
export function deletePermissionPending(
|
||||
permission: Permission,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
): Action {
|
||||
return {
|
||||
type: DELETE_PERMISSION_PENDING,
|
||||
payload: permission,
|
||||
itemId: createItemId(permission, namespace, repoName)
|
||||
itemId: createItemId(permission, namespace, repoName),
|
||||
};
|
||||
}
|
||||
|
||||
export function deletePermissionSuccess(
|
||||
permission: Permission,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
): Action {
|
||||
return {
|
||||
type: DELETE_PERMISSION_SUCCESS,
|
||||
payload: {
|
||||
permission,
|
||||
position: namespace + "/" + repoName
|
||||
position: namespace + '/' + repoName,
|
||||
},
|
||||
itemId: createItemId(permission, namespace, repoName)
|
||||
itemId: createItemId(permission, namespace, repoName),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -444,15 +409,15 @@ export function deletePermissionFailure(
|
||||
permission: Permission,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
error: Error
|
||||
error: Error,
|
||||
): Action {
|
||||
return {
|
||||
type: DELETE_PERMISSION_FAILURE,
|
||||
payload: {
|
||||
error,
|
||||
permission
|
||||
permission,
|
||||
},
|
||||
itemId: createItemId(permission, namespace, repoName)
|
||||
itemId: createItemId(permission, namespace, repoName),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -461,15 +426,15 @@ export function deletePermissionReset(namespace: string, repoName: string) {
|
||||
type: DELETE_PERMISSION_RESET,
|
||||
payload: {
|
||||
namespace,
|
||||
repoName
|
||||
repoName,
|
||||
},
|
||||
itemId: namespace + "/" + repoName
|
||||
itemId: namespace + '/' + repoName,
|
||||
};
|
||||
}
|
||||
|
||||
function deletePermissionFromState(
|
||||
oldPermissions: PermissionCollection,
|
||||
permission: Permission
|
||||
permission: Permission,
|
||||
) {
|
||||
let newPermission = [];
|
||||
for (let i = 0; i < oldPermissions.length; i++) {
|
||||
@@ -486,17 +451,19 @@ function deletePermissionFromState(
|
||||
function createItemId(
|
||||
permission: Permission,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
) {
|
||||
let groupPermission = permission.groupPermission ? "@" : "";
|
||||
return namespace + "/" + repoName + "/" + groupPermission + permission.name;
|
||||
let groupPermission = permission.groupPermission ? '@' : '';
|
||||
return namespace + '/' + repoName + '/' + groupPermission + permission.name;
|
||||
}
|
||||
|
||||
// reducer
|
||||
export default function reducer(
|
||||
state: Object = {},
|
||||
action: Action = { type: "UNKNOWN" }
|
||||
): Object {
|
||||
state: object = {},
|
||||
action: Action = {
|
||||
type: 'UNKNOWN',
|
||||
},
|
||||
): object {
|
||||
if (!action.payload) {
|
||||
return state;
|
||||
}
|
||||
@@ -504,28 +471,28 @@ export default function reducer(
|
||||
case FETCH_AVAILABLE_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
available: action.payload
|
||||
available: action.payload,
|
||||
};
|
||||
case FETCH_PERMISSIONS_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
[action.itemId]: {
|
||||
entries: action.payload._embedded.permissions,
|
||||
createPermission: !!action.payload._links.create
|
||||
}
|
||||
createPermission: !!action.payload._links.create,
|
||||
},
|
||||
};
|
||||
case MODIFY_PERMISSION_SUCCESS:
|
||||
const positionOfPermission = action.payload.position;
|
||||
const newPermission = newPermissions(
|
||||
state[action.payload.position].entries,
|
||||
action.payload.permission
|
||||
action.payload.permission,
|
||||
);
|
||||
return {
|
||||
...state,
|
||||
[positionOfPermission]: {
|
||||
...state[positionOfPermission],
|
||||
entries: newPermission
|
||||
}
|
||||
entries: newPermission,
|
||||
},
|
||||
};
|
||||
case CREATE_PERMISSION_SUCCESS:
|
||||
// return state;
|
||||
@@ -536,21 +503,21 @@ export default function reducer(
|
||||
...state,
|
||||
[position]: {
|
||||
...state[position],
|
||||
entries: permissions
|
||||
}
|
||||
entries: permissions,
|
||||
},
|
||||
};
|
||||
case DELETE_PERMISSION_SUCCESS:
|
||||
const permissionPosition = action.payload.position;
|
||||
const new_Permissions = deletePermissionFromState(
|
||||
state[action.payload.position].entries,
|
||||
action.payload.permission
|
||||
action.payload.permission,
|
||||
);
|
||||
return {
|
||||
...state,
|
||||
[permissionPosition]: {
|
||||
...state[permissionPosition],
|
||||
entries: new_Permissions
|
||||
}
|
||||
entries: new_Permissions,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
@@ -559,21 +526,21 @@ export default function reducer(
|
||||
|
||||
// selectors
|
||||
|
||||
export function getAvailablePermissions(state: Object) {
|
||||
export function getAvailablePermissions(state: object) {
|
||||
if (state.permissions) {
|
||||
return state.permissions.available;
|
||||
}
|
||||
}
|
||||
|
||||
export function getAvailableRepositoryRoles(state: Object) {
|
||||
export function getAvailableRepositoryRoles(state: object) {
|
||||
return available(state).repositoryRoles;
|
||||
}
|
||||
|
||||
export function getAvailableRepositoryVerbs(state: Object) {
|
||||
export function getAvailableRepositoryVerbs(state: object) {
|
||||
return available(state).repositoryVerbs;
|
||||
}
|
||||
|
||||
function available(state: Object) {
|
||||
function available(state: object) {
|
||||
if (state.permissions && state.permissions.available) {
|
||||
return state.permissions.available;
|
||||
}
|
||||
@@ -581,125 +548,125 @@ function available(state: Object) {
|
||||
}
|
||||
|
||||
export function getPermissionsOfRepo(
|
||||
state: Object,
|
||||
state: object,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
) {
|
||||
if (state.permissions && state.permissions[namespace + "/" + repoName]) {
|
||||
return state.permissions[namespace + "/" + repoName].entries;
|
||||
if (state.permissions && state.permissions[namespace + '/' + repoName]) {
|
||||
return state.permissions[namespace + '/' + repoName].entries;
|
||||
}
|
||||
}
|
||||
|
||||
export function isFetchAvailablePermissionsPending(state: Object) {
|
||||
return isPending(state, FETCH_AVAILABLE, "available");
|
||||
export function isFetchAvailablePermissionsPending(state: object) {
|
||||
return isPending(state, FETCH_AVAILABLE, 'available');
|
||||
}
|
||||
|
||||
export function isFetchPermissionsPending(
|
||||
state: Object,
|
||||
state: object,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
) {
|
||||
return isPending(state, FETCH_PERMISSIONS, namespace + "/" + repoName);
|
||||
return isPending(state, FETCH_PERMISSIONS, namespace + '/' + repoName);
|
||||
}
|
||||
|
||||
export function getFetchAvailablePermissionsFailure(state: Object) {
|
||||
return getFailure(state, FETCH_AVAILABLE, "available");
|
||||
export function getFetchAvailablePermissionsFailure(state: object) {
|
||||
return getFailure(state, FETCH_AVAILABLE, 'available');
|
||||
}
|
||||
|
||||
export function getFetchPermissionsFailure(
|
||||
state: Object,
|
||||
state: object,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
) {
|
||||
return getFailure(state, FETCH_PERMISSIONS, namespace + "/" + repoName);
|
||||
return getFailure(state, FETCH_PERMISSIONS, namespace + '/' + repoName);
|
||||
}
|
||||
|
||||
export function isModifyPermissionPending(
|
||||
state: Object,
|
||||
state: object,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
permission: Permission
|
||||
permission: Permission,
|
||||
) {
|
||||
return isPending(
|
||||
state,
|
||||
MODIFY_PERMISSION,
|
||||
createItemId(permission, namespace, repoName)
|
||||
createItemId(permission, namespace, repoName),
|
||||
);
|
||||
}
|
||||
|
||||
export function getModifyPermissionFailure(
|
||||
state: Object,
|
||||
state: object,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
permission: Permission
|
||||
permission: Permission,
|
||||
) {
|
||||
return getFailure(
|
||||
state,
|
||||
MODIFY_PERMISSION,
|
||||
createItemId(permission, namespace, repoName)
|
||||
createItemId(permission, namespace, repoName),
|
||||
);
|
||||
}
|
||||
|
||||
export function hasCreatePermission(
|
||||
state: Object,
|
||||
state: object,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
) {
|
||||
if (state.permissions && state.permissions[namespace + "/" + repoName])
|
||||
return state.permissions[namespace + "/" + repoName].createPermission;
|
||||
if (state.permissions && state.permissions[namespace + '/' + repoName])
|
||||
return state.permissions[namespace + '/' + repoName].createPermission;
|
||||
else return null;
|
||||
}
|
||||
|
||||
export function isCreatePermissionPending(
|
||||
state: Object,
|
||||
state: object,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
) {
|
||||
return isPending(state, CREATE_PERMISSION, namespace + "/" + repoName);
|
||||
return isPending(state, CREATE_PERMISSION, namespace + '/' + repoName);
|
||||
}
|
||||
|
||||
export function getCreatePermissionFailure(
|
||||
state: Object,
|
||||
state: object,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
) {
|
||||
return getFailure(state, CREATE_PERMISSION, namespace + "/" + repoName);
|
||||
return getFailure(state, CREATE_PERMISSION, namespace + '/' + repoName);
|
||||
}
|
||||
|
||||
export function isDeletePermissionPending(
|
||||
state: Object,
|
||||
state: object,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
permission: Permission
|
||||
permission: Permission,
|
||||
) {
|
||||
return isPending(
|
||||
state,
|
||||
DELETE_PERMISSION,
|
||||
createItemId(permission, namespace, repoName)
|
||||
createItemId(permission, namespace, repoName),
|
||||
);
|
||||
}
|
||||
|
||||
export function getDeletePermissionFailure(
|
||||
state: Object,
|
||||
state: object,
|
||||
namespace: string,
|
||||
repoName: string,
|
||||
permission: Permission
|
||||
permission: Permission,
|
||||
) {
|
||||
return getFailure(
|
||||
state,
|
||||
DELETE_PERMISSION,
|
||||
createItemId(permission, namespace, repoName)
|
||||
createItemId(permission, namespace, repoName),
|
||||
);
|
||||
}
|
||||
|
||||
export function getDeletePermissionsFailure(
|
||||
state: Object,
|
||||
state: object,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
) {
|
||||
const permissions =
|
||||
state.permissions && state.permissions[namespace + "/" + repoName]
|
||||
? state.permissions[namespace + "/" + repoName].entries
|
||||
state.permissions && state.permissions[namespace + '/' + repoName]
|
||||
? state.permissions[namespace + '/' + repoName].entries
|
||||
: null;
|
||||
if (permissions == null) return undefined;
|
||||
for (let i = 0; i < permissions.length; i++) {
|
||||
@@ -709,7 +676,7 @@ export function getDeletePermissionsFailure(
|
||||
return getFailure(
|
||||
state,
|
||||
DELETE_PERMISSION,
|
||||
createItemId(permissions[i], namespace, repoName)
|
||||
createItemId(permissions[i], namespace, repoName),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -717,13 +684,13 @@ export function getDeletePermissionsFailure(
|
||||
}
|
||||
|
||||
export function getModifyPermissionsFailure(
|
||||
state: Object,
|
||||
state: object,
|
||||
namespace: string,
|
||||
repoName: string
|
||||
repoName: string,
|
||||
) {
|
||||
const permissions =
|
||||
state.permissions && state.permissions[namespace + "/" + repoName]
|
||||
? state.permissions[namespace + "/" + repoName].entries
|
||||
state.permissions && state.permissions[namespace + '/' + repoName]
|
||||
? state.permissions[namespace + '/' + repoName].entries
|
||||
: null;
|
||||
if (permissions == null) return undefined;
|
||||
for (let i = 0; i < permissions.length; i++) {
|
||||
@@ -733,7 +700,7 @@ export function getModifyPermissionsFailure(
|
||||
return getFailure(
|
||||
state,
|
||||
MODIFY_PERMISSION,
|
||||
createItemId(permissions[i], namespace, repoName)
|
||||
createItemId(permissions[i], namespace, repoName),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -742,10 +709,10 @@ export function getModifyPermissionsFailure(
|
||||
|
||||
export function findVerbsForRole(
|
||||
availableRepositoryRoles: RepositoryRole[],
|
||||
roleName: string
|
||||
roleName: string,
|
||||
) {
|
||||
const matchingRole = availableRepositoryRoles.find(
|
||||
role => roleName === role.name
|
||||
role => roleName === role.name,
|
||||
);
|
||||
if (matchingRole) {
|
||||
return matchingRole.verbs;
|
||||
@@ -1,19 +1,18 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import type { File } from "@scm-manager/ui-types";
|
||||
import React from 'react';
|
||||
import { File } from '@scm-manager/ui-types';
|
||||
|
||||
type Props = {
|
||||
file: File
|
||||
file: File;
|
||||
};
|
||||
|
||||
class FileIcon extends React.Component<Props> {
|
||||
render() {
|
||||
const { file } = this.props;
|
||||
let icon = "file";
|
||||
let icon = 'file';
|
||||
if (file.subRepository) {
|
||||
icon = "folder-plus";
|
||||
icon = 'folder-plus';
|
||||
} else if (file.directory) {
|
||||
icon = "folder";
|
||||
icon = 'folder';
|
||||
}
|
||||
return <i className={`fa fa-${icon}`} />;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { findParent } from "./FileTree";
|
||||
|
||||
describe("find parent tests", () => {
|
||||
it("should return the parent path", () => {
|
||||
expect(findParent("src/main/js/")).toBe("src/main");
|
||||
expect(findParent("src/main/js")).toBe("src/main");
|
||||
expect(findParent("src/main")).toBe("src");
|
||||
expect(findParent("src")).toBe("");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import { findParent } from './FileTree';
|
||||
|
||||
describe('find parent tests', () => {
|
||||
it('should return the parent path', () => {
|
||||
expect(findParent('src/main/js/')).toBe('src/main');
|
||||
expect(findParent('src/main/js')).toBe('src/main');
|
||||
expect(findParent('src/main')).toBe('src');
|
||||
expect(findParent('src')).toBe('');
|
||||
});
|
||||
});
|
||||
@@ -1,36 +1,35 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { compose } from "redux";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import { translate } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { binder } from "@scm-manager/ui-extensions";
|
||||
import type { Repository, File } from "@scm-manager/ui-types";
|
||||
import React from 'react';
|
||||
import { compose } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { translate } from 'react-i18next';
|
||||
import styled from 'styled-components';
|
||||
import { binder } from '@scm-manager/ui-extensions';
|
||||
import { Repository, File } from '@scm-manager/ui-types';
|
||||
import {
|
||||
ErrorNotification,
|
||||
Loading,
|
||||
Notification
|
||||
} from "@scm-manager/ui-components";
|
||||
Notification,
|
||||
} from '@scm-manager/ui-components';
|
||||
import {
|
||||
getFetchSourcesFailure,
|
||||
isFetchSourcesPending,
|
||||
getSources
|
||||
} from "../modules/sources";
|
||||
import FileTreeLeaf from "./FileTreeLeaf";
|
||||
getSources,
|
||||
} from '../modules/sources';
|
||||
import FileTreeLeaf from './FileTreeLeaf';
|
||||
|
||||
type Props = {
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
tree: File,
|
||||
repository: Repository,
|
||||
revision: string,
|
||||
path: string,
|
||||
baseUrl: string,
|
||||
loading: boolean;
|
||||
error: Error;
|
||||
tree: File;
|
||||
repository: Repository;
|
||||
revision: string;
|
||||
path: string;
|
||||
baseUrl: string;
|
||||
|
||||
// context props
|
||||
t: string => string,
|
||||
match: any
|
||||
t: (p: string) => string;
|
||||
match: any;
|
||||
};
|
||||
|
||||
const FixedWidthTh = styled.th`
|
||||
@@ -38,15 +37,15 @@ const FixedWidthTh = styled.th`
|
||||
`;
|
||||
|
||||
export function findParent(path: string) {
|
||||
if (path.endsWith("/")) {
|
||||
if (path.endsWith('/')) {
|
||||
path = path.substring(0, path.length - 1);
|
||||
}
|
||||
|
||||
const index = path.lastIndexOf("/");
|
||||
const index = path.lastIndexOf('/');
|
||||
if (index > 0) {
|
||||
return path.substring(0, index);
|
||||
}
|
||||
return "";
|
||||
return '';
|
||||
}
|
||||
|
||||
class FileTree extends React.Component<Props> {
|
||||
@@ -74,9 +73,9 @@ class FileTree extends React.Component<Props> {
|
||||
|
||||
if (path) {
|
||||
files.push({
|
||||
name: "..",
|
||||
name: '..',
|
||||
path: findParent(path),
|
||||
directory: true
|
||||
directory: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -103,9 +102,9 @@ class FileTree extends React.Component<Props> {
|
||||
if (files && files.length > 0) {
|
||||
let baseUrlWithRevision = baseUrl;
|
||||
if (revision) {
|
||||
baseUrlWithRevision += "/" + encodeURIComponent(revision);
|
||||
baseUrlWithRevision += '/' + encodeURIComponent(revision);
|
||||
} else {
|
||||
baseUrlWithRevision += "/" + encodeURIComponent(tree.revision);
|
||||
baseUrlWithRevision += '/' + encodeURIComponent(tree.revision);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -113,17 +112,17 @@ class FileTree extends React.Component<Props> {
|
||||
<thead>
|
||||
<tr>
|
||||
<FixedWidthTh />
|
||||
<th>{t("sources.file-tree.name")}</th>
|
||||
<th>{t('sources.file-tree.name')}</th>
|
||||
<th className="is-hidden-mobile">
|
||||
{t("sources.file-tree.length")}
|
||||
{t('sources.file-tree.length')}
|
||||
</th>
|
||||
<th className="is-hidden-mobile">
|
||||
{t("sources.file-tree.lastModified")}
|
||||
{t('sources.file-tree.lastModified')}
|
||||
</th>
|
||||
<th className="is-hidden-mobile">
|
||||
{t("sources.file-tree.description")}
|
||||
{t('sources.file-tree.description')}
|
||||
</th>
|
||||
{binder.hasExtension("repos.sources.tree.row.right") && (
|
||||
{binder.hasExtension('repos.sources.tree.row.right') && (
|
||||
<th className="is-hidden-mobile" />
|
||||
)}
|
||||
</tr>
|
||||
@@ -140,7 +139,7 @@ class FileTree extends React.Component<Props> {
|
||||
</table>
|
||||
);
|
||||
}
|
||||
return <Notification type="info">{t("sources.noSources")}</Notification>;
|
||||
return <Notification type="info">{t('sources.noSources')}</Notification>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,11 +155,11 @@ const mapStateToProps = (state: any, ownProps: Props) => {
|
||||
path,
|
||||
loading,
|
||||
error,
|
||||
tree
|
||||
tree,
|
||||
};
|
||||
};
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps)
|
||||
)(translate("repos")(FileTree));
|
||||
connect(mapStateToProps),
|
||||
)(translate('repos')(FileTree));
|
||||
@@ -1,30 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { createLink } from "./FileTreeLeaf";
|
||||
import type { File } from "@scm-manager/ui-types";
|
||||
|
||||
describe("create link tests", () => {
|
||||
function dir(path: string): File {
|
||||
return {
|
||||
name: "dir",
|
||||
path: path,
|
||||
directory: true,
|
||||
length: 1,
|
||||
revision: "1a",
|
||||
_links: {},
|
||||
_embedded: {
|
||||
children: []
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
it("should create link", () => {
|
||||
expect(createLink("src", dir("main"))).toBe("src/main/");
|
||||
expect(createLink("src", dir("/main"))).toBe("src/main/");
|
||||
expect(createLink("src", dir("/main/"))).toBe("src/main/");
|
||||
});
|
||||
|
||||
it("should return base url if the directory path is empty", () => {
|
||||
expect(createLink("src", dir(""))).toBe("src/");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { createLink } from './FileTreeLeaf';
|
||||
import { File } from '@scm-manager/ui-types';
|
||||
|
||||
describe('create link tests', () => {
|
||||
function dir(path: string): File {
|
||||
return {
|
||||
name: 'dir',
|
||||
path: path,
|
||||
directory: true,
|
||||
length: 1,
|
||||
revision: '1a',
|
||||
_links: {},
|
||||
_embedded: {
|
||||
children: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
it('should create link', () => {
|
||||
expect(createLink('src', dir('main'))).toBe('src/main/');
|
||||
expect(createLink('src', dir('/main'))).toBe('src/main/');
|
||||
expect(createLink('src', dir('/main/'))).toBe('src/main/');
|
||||
});
|
||||
|
||||
it('should return base url if the directory path is empty', () => {
|
||||
expect(createLink('src', dir(''))).toBe('src/');
|
||||
});
|
||||
});
|
||||
@@ -1,16 +1,15 @@
|
||||
//@flow
|
||||
import * as React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import type { File } from "@scm-manager/ui-types";
|
||||
import { DateFromNow, FileSize } from "@scm-manager/ui-components";
|
||||
import FileIcon from "./FileIcon";
|
||||
import * as React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import classNames from 'classnames';
|
||||
import styled from 'styled-components';
|
||||
import { binder, ExtensionPoint } from '@scm-manager/ui-extensions';
|
||||
import { File } from '@scm-manager/ui-types';
|
||||
import { DateFromNow, FileSize } from '@scm-manager/ui-components';
|
||||
import FileIcon from './FileIcon';
|
||||
|
||||
type Props = {
|
||||
file: File,
|
||||
baseUrl: string
|
||||
file: File;
|
||||
baseUrl: string;
|
||||
};
|
||||
|
||||
const MinWidthTd = styled.td`
|
||||
@@ -21,13 +20,13 @@ export function createLink(base: string, file: File) {
|
||||
let link = base;
|
||||
if (file.path) {
|
||||
let path = file.path;
|
||||
if (path.startsWith("/")) {
|
||||
if (path.startsWith('/')) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
link += "/" + path;
|
||||
link += '/' + path;
|
||||
}
|
||||
if (!link.endsWith("/")) {
|
||||
link += "/";
|
||||
if (!link.endsWith('/')) {
|
||||
link += '/';
|
||||
}
|
||||
return link;
|
||||
}
|
||||
@@ -62,7 +61,7 @@ export default class FileTreeLeaf extends React.Component<Props> {
|
||||
render() {
|
||||
const { file } = this.props;
|
||||
|
||||
const fileSize = file.directory ? "" : <FileSize bytes={file.length} />;
|
||||
const fileSize = file.directory ? '' : <FileSize bytes={file.length} />;
|
||||
|
||||
return (
|
||||
<tr>
|
||||
@@ -74,15 +73,17 @@ export default class FileTreeLeaf extends React.Component<Props> {
|
||||
<td className="is-hidden-mobile">
|
||||
<DateFromNow date={file.lastModified} />
|
||||
</td>
|
||||
<MinWidthTd className={classNames("is-word-break", "is-hidden-mobile")}>
|
||||
<MinWidthTd className={classNames('is-word-break', 'is-hidden-mobile')}>
|
||||
{file.description}
|
||||
</MinWidthTd>
|
||||
{binder.hasExtension("repos.sources.tree.row.right") && (
|
||||
{binder.hasExtension('repos.sources.tree.row.right') && (
|
||||
<td className="is-hidden-mobile">
|
||||
{!file.directory && (
|
||||
<ExtensionPoint
|
||||
name="repos.sources.tree.row.right"
|
||||
props={{ file }}
|
||||
props={{
|
||||
file,
|
||||
}}
|
||||
renderAll={true}
|
||||
/>
|
||||
)}
|
||||
@@ -1,26 +0,0 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { File } from "@scm-manager/ui-types";
|
||||
import { DownloadButton } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
file: File
|
||||
};
|
||||
|
||||
class DownloadViewer extends React.Component<Props> {
|
||||
render() {
|
||||
const { t, file } = this.props;
|
||||
return (
|
||||
<div className="has-text-centered">
|
||||
<DownloadButton
|
||||
url={file._links.self.href}
|
||||
displayName={t("sources.content.downloadButton")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(DownloadViewer);
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import { translate } from 'react-i18next';
|
||||
import { File } from '@scm-manager/ui-types';
|
||||
import { DownloadButton } from '@scm-manager/ui-components';
|
||||
|
||||
type Props = {
|
||||
t: (p: string) => string;
|
||||
file: File;
|
||||
};
|
||||
|
||||
class DownloadViewer extends React.Component<Props> {
|
||||
render() {
|
||||
const { t, file } = this.props;
|
||||
return (
|
||||
<div className="has-text-centered">
|
||||
<DownloadButton
|
||||
url={file._links.self.href}
|
||||
displayName={t('sources.content.downloadButton')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate('repos')(DownloadViewer);
|
||||
@@ -1,13 +1,12 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { ButtonAddons, Button } from "@scm-manager/ui-components";
|
||||
import React from 'react';
|
||||
import { translate } from 'react-i18next';
|
||||
import { ButtonAddons, Button } from '@scm-manager/ui-components';
|
||||
|
||||
type Props = {
|
||||
className?: string,
|
||||
t: string => string,
|
||||
historyIsSelected: boolean,
|
||||
showHistory: boolean => void
|
||||
className?: string;
|
||||
t: (p: string) => string;
|
||||
historyIsSelected: boolean;
|
||||
showHistory: (p: boolean) => void;
|
||||
};
|
||||
|
||||
class FileButtonAddons extends React.Component<Props> {
|
||||
@@ -20,7 +19,7 @@ class FileButtonAddons extends React.Component<Props> {
|
||||
};
|
||||
|
||||
color = (selected: boolean) => {
|
||||
return selected ? "link is-selected" : null;
|
||||
return selected ? 'link is-selected' : null;
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -28,7 +27,7 @@ class FileButtonAddons extends React.Component<Props> {
|
||||
|
||||
return (
|
||||
<ButtonAddons className={className}>
|
||||
<div title={t("sources.content.sourcesButton")}>
|
||||
<div title={t('sources.content.sourcesButton')}>
|
||||
<Button
|
||||
action={this.showSources}
|
||||
className="reduced"
|
||||
@@ -39,7 +38,7 @@ class FileButtonAddons extends React.Component<Props> {
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div title={t("sources.content.historyButton")}>
|
||||
<div title={t('sources.content.historyButton')}>
|
||||
<Button
|
||||
action={this.showHistory}
|
||||
className="reduced"
|
||||
@@ -55,4 +54,4 @@ class FileButtonAddons extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(FileButtonAddons);
|
||||
export default translate('repos')(FileButtonAddons);
|
||||
@@ -1,11 +1,10 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { File } from "@scm-manager/ui-types";
|
||||
import React from 'react';
|
||||
import { translate } from 'react-i18next';
|
||||
import { File } from '@scm-manager/ui-types';
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
file: File
|
||||
t: (p: string) => string;
|
||||
file: File;
|
||||
};
|
||||
|
||||
class ImageViewer extends React.Component<Props> {
|
||||
@@ -21,4 +20,4 @@ class ImageViewer extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(ImageViewer);
|
||||
export default translate('repos')(ImageViewer);
|
||||
@@ -1,33 +0,0 @@
|
||||
//@flow
|
||||
import fetchMock from "fetch-mock";
|
||||
import {
|
||||
getContent,
|
||||
getLanguage
|
||||
} from "./SourcecodeViewer";
|
||||
|
||||
describe("get content", () => {
|
||||
const CONTENT_URL = "/repositories/scmadmin/TestRepo/content/testContent";
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it("should return content", done => {
|
||||
fetchMock.getOnce("/api/v2" + CONTENT_URL, "This is a testContent");
|
||||
|
||||
getContent(CONTENT_URL).then(content => {
|
||||
expect(content).toBe("This is a testContent");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("get correct language type", () => {
|
||||
it("should return javascript", () => {
|
||||
expect(getLanguage("JAVASCRIPT")).toBe("javascript");
|
||||
});
|
||||
it("should return nothing for plain text", () => {
|
||||
expect(getLanguage("")).toBe("");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { getContent, getLanguage } from './SourcecodeViewer';
|
||||
|
||||
describe('get content', () => {
|
||||
const CONTENT_URL = '/repositories/scmadmin/TestRepo/content/testContent';
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it('should return content', done => {
|
||||
fetchMock.getOnce('/api/v2' + CONTENT_URL, 'This is a testContent');
|
||||
|
||||
getContent(CONTENT_URL).then(content => {
|
||||
expect(content).toBe('This is a testContent');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('get correct language type', () => {
|
||||
it('should return javascript', () => {
|
||||
expect(getLanguage('JAVASCRIPT')).toBe('javascript');
|
||||
});
|
||||
it('should return nothing for plain text', () => {
|
||||
expect(getLanguage('')).toBe('');
|
||||
});
|
||||
});
|
||||
@@ -1,20 +1,19 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { apiClient, SyntaxHighlighter } from "@scm-manager/ui-components";
|
||||
import type { File } from "@scm-manager/ui-types";
|
||||
import { ErrorNotification, Loading } from "@scm-manager/ui-components";
|
||||
import React from 'react';
|
||||
import { translate } from 'react-i18next';
|
||||
import { apiClient, SyntaxHighlighter } from '@scm-manager/ui-components';
|
||||
import { File } from '@scm-manager/ui-types';
|
||||
import { ErrorNotification, Loading } from '@scm-manager/ui-components';
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
file: File,
|
||||
language: string
|
||||
t: (p: string) => string;
|
||||
file: File;
|
||||
language: string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
content: string,
|
||||
error?: Error,
|
||||
loaded: boolean
|
||||
content: string;
|
||||
error?: Error;
|
||||
loaded: boolean;
|
||||
};
|
||||
|
||||
class SourcecodeViewer extends React.Component<Props, State> {
|
||||
@@ -22,8 +21,8 @@ class SourcecodeViewer extends React.Component<Props, State> {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
content: "",
|
||||
loaded: false
|
||||
content: '',
|
||||
loaded: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,13 +34,13 @@ class SourcecodeViewer extends React.Component<Props, State> {
|
||||
this.setState({
|
||||
...this.state,
|
||||
error: result.error,
|
||||
loaded: true
|
||||
loaded: true,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
...this.state,
|
||||
content: result,
|
||||
loaded: true
|
||||
loaded: true,
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -65,10 +64,7 @@ class SourcecodeViewer extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
return (
|
||||
<SyntaxHighlighter
|
||||
language={getLanguage(language)}
|
||||
value= {content}
|
||||
/>
|
||||
<SyntaxHighlighter language={getLanguage(language)} value={content} />
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -85,8 +81,10 @@ export function getContent(url: string) {
|
||||
return response;
|
||||
})
|
||||
.catch(err => {
|
||||
return { error: err };
|
||||
return {
|
||||
error: err,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export default translate("repos")(SourcecodeViewer);
|
||||
export default translate('repos')(SourcecodeViewer);
|
||||
@@ -1,32 +1,36 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import {connect} from "react-redux";
|
||||
import {translate} from "react-i18next";
|
||||
import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
import {ExtensionPoint} from "@scm-manager/ui-extensions";
|
||||
import type {File, Repository} from "@scm-manager/ui-types";
|
||||
import {DateFromNow, ErrorNotification, FileSize, Icon} from "@scm-manager/ui-components";
|
||||
import {getSources} from "../modules/sources";
|
||||
import FileButtonAddons from "../components/content/FileButtonAddons";
|
||||
import SourcesView from "./SourcesView";
|
||||
import HistoryView from "./HistoryView";
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { translate } from 'react-i18next';
|
||||
import classNames from 'classnames';
|
||||
import styled from 'styled-components';
|
||||
import { ExtensionPoint } from '@scm-manager/ui-extensions';
|
||||
import { File, Repository } from '@scm-manager/ui-types';
|
||||
import {
|
||||
DateFromNow,
|
||||
ErrorNotification,
|
||||
FileSize,
|
||||
Icon,
|
||||
} from '@scm-manager/ui-components';
|
||||
import { getSources } from '../modules/sources';
|
||||
import FileButtonAddons from '../components/content/FileButtonAddons';
|
||||
import SourcesView from './SourcesView';
|
||||
import HistoryView from './HistoryView';
|
||||
|
||||
type Props = {
|
||||
loading: boolean,
|
||||
file: File,
|
||||
repository: Repository,
|
||||
revision: string,
|
||||
path: string,
|
||||
loading: boolean;
|
||||
file: File;
|
||||
repository: Repository;
|
||||
revision: string;
|
||||
path: string;
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
t: (p: string) => string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
collapsed: boolean,
|
||||
showHistory: boolean,
|
||||
errorFromExtension?: Error
|
||||
collapsed: boolean;
|
||||
showHistory: boolean;
|
||||
errorFromExtension?: Error;
|
||||
};
|
||||
|
||||
const VCenteredChild = styled.div`
|
||||
@@ -55,31 +59,33 @@ class Content extends React.Component<Props, State> {
|
||||
|
||||
this.state = {
|
||||
collapsed: true,
|
||||
showHistory: false
|
||||
showHistory: false,
|
||||
};
|
||||
}
|
||||
|
||||
toggleCollapse = () => {
|
||||
this.setState(prevState => ({
|
||||
collapsed: !prevState.collapsed
|
||||
collapsed: !prevState.collapsed,
|
||||
}));
|
||||
};
|
||||
|
||||
setShowHistoryState(showHistory: boolean) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
showHistory
|
||||
showHistory,
|
||||
});
|
||||
}
|
||||
|
||||
handleExtensionError = (error: Error) => {
|
||||
this.setState({ errorFromExtension: error });
|
||||
this.setState({
|
||||
errorFromExtension: error,
|
||||
});
|
||||
};
|
||||
|
||||
showHeader() {
|
||||
const { file, revision } = this.props;
|
||||
const { showHistory, collapsed } = this.state;
|
||||
const icon = collapsed ? "angle-right" : "angle-down";
|
||||
const icon = collapsed ? 'angle-right' : 'angle-down';
|
||||
|
||||
const selector = file._links.history ? (
|
||||
<RightMarginFileButtonAddons
|
||||
@@ -93,7 +99,7 @@ class Content extends React.Component<Props, State> {
|
||||
|
||||
return (
|
||||
<span className="has-cursor-pointer">
|
||||
<VCenteredChild className={classNames("media", "is-flex")}>
|
||||
<VCenteredChild className={classNames('media', 'is-flex')}>
|
||||
<div className="media-content" onClick={this.toggleCollapse}>
|
||||
<RightMarginIcon name={icon} color="inherit" />
|
||||
<span className="is-word-break">{file.name}</span>
|
||||
@@ -105,7 +111,7 @@ class Content extends React.Component<Props, State> {
|
||||
props={{
|
||||
file,
|
||||
revision,
|
||||
handleExtensionError: this.handleExtensionError
|
||||
handleExtensionError: this.handleExtensionError,
|
||||
}}
|
||||
renderAll={true}
|
||||
/>
|
||||
@@ -121,7 +127,7 @@ class Content extends React.Component<Props, State> {
|
||||
const date = <DateFromNow date={file.lastModified} />;
|
||||
const description = file.description ? (
|
||||
<p>
|
||||
{file.description.split("\n").map((item, key) => {
|
||||
{file.description.split('\n').map((item, key) => {
|
||||
return (
|
||||
<span key={key}>
|
||||
{item}
|
||||
@@ -131,36 +137,40 @@ class Content extends React.Component<Props, State> {
|
||||
})}
|
||||
</p>
|
||||
) : null;
|
||||
const fileSize = file.directory ? "" : <FileSize bytes={file.length} />;
|
||||
const fileSize = file.directory ? '' : <FileSize bytes={file.length} />;
|
||||
if (!collapsed) {
|
||||
return (
|
||||
<LighterGreyBackgroundPanelBlock className="panel-block">
|
||||
<LighterGreyBackgroundTable className="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{t("sources.content.path")}</td>
|
||||
<td>{t('sources.content.path')}</td>
|
||||
<td className="is-word-break">{file.path}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("sources.content.branch")}</td>
|
||||
<td>{t('sources.content.branch')}</td>
|
||||
<td className="is-word-break">{revision}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("sources.content.size")}</td>
|
||||
<td>{t('sources.content.size')}</td>
|
||||
<td>{fileSize}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("sources.content.lastModified")}</td>
|
||||
<td>{t('sources.content.lastModified')}</td>
|
||||
<td>{date}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("sources.content.description")}</td>
|
||||
<td>{t('sources.content.description')}</td>
|
||||
<td className="is-word-break">{description}</td>
|
||||
</tr>
|
||||
<ExtensionPoint
|
||||
name="repos.content.metadata"
|
||||
renderAll={true}
|
||||
props={{ file, repository, revision }}
|
||||
props={{
|
||||
file,
|
||||
repository,
|
||||
revision,
|
||||
}}
|
||||
/>
|
||||
</tbody>
|
||||
</LighterGreyBackgroundTable>
|
||||
@@ -207,8 +217,8 @@ const mapStateToProps = (state: any, ownProps: Props) => {
|
||||
const file = getSources(state, repository, revision, path);
|
||||
|
||||
return {
|
||||
file
|
||||
file,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(translate("repos")(Content));
|
||||
export default connect(mapStateToProps)(translate('repos')(Content));
|
||||
@@ -1,30 +1,29 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import type {
|
||||
import React from 'react';
|
||||
import {
|
||||
File,
|
||||
Changeset,
|
||||
Repository,
|
||||
PagedCollection
|
||||
} from "@scm-manager/ui-types";
|
||||
PagedCollection,
|
||||
} from '@scm-manager/ui-types';
|
||||
import {
|
||||
ErrorNotification,
|
||||
Loading,
|
||||
StatePaginator,
|
||||
ChangesetList
|
||||
} from "@scm-manager/ui-components";
|
||||
import { getHistory } from "./history";
|
||||
ChangesetList,
|
||||
} from '@scm-manager/ui-components';
|
||||
import { getHistory } from './history';
|
||||
|
||||
type Props = {
|
||||
file: File,
|
||||
repository: Repository
|
||||
file: File;
|
||||
repository: Repository;
|
||||
};
|
||||
|
||||
type State = {
|
||||
loaded: boolean,
|
||||
changesets: Changeset[],
|
||||
page: number,
|
||||
pageCollection?: PagedCollection,
|
||||
error?: Error
|
||||
loaded: boolean;
|
||||
changesets: Changeset[];
|
||||
page: number;
|
||||
pageCollection?: PagedCollection;
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
class HistoryView extends React.Component<Props, State> {
|
||||
@@ -34,7 +33,7 @@ class HistoryView extends React.Component<Props, State> {
|
||||
this.state = {
|
||||
loaded: false,
|
||||
page: 1,
|
||||
changesets: []
|
||||
changesets: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -50,7 +49,7 @@ class HistoryView extends React.Component<Props, State> {
|
||||
this.setState({
|
||||
...this.state,
|
||||
error: result.error,
|
||||
loaded: true
|
||||
loaded: true,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
@@ -58,7 +57,7 @@ class HistoryView extends React.Component<Props, State> {
|
||||
loaded: true,
|
||||
changesets: result.changesets,
|
||||
pageCollection: result.pageCollection,
|
||||
page: result.pageCollection.page
|
||||
page: result.pageCollection.page,
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -69,7 +68,7 @@ class HistoryView extends React.Component<Props, State> {
|
||||
const { file } = this.props;
|
||||
const internalPage = page - 1;
|
||||
this.updateHistory(
|
||||
file._links.history.href + "?page=" + internalPage.toString()
|
||||
file._links.history.href + '?page=' + internalPage.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,49 +1,48 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import type { Branch, Repository } from "@scm-manager/ui-types";
|
||||
import FileTree from "../components/FileTree";
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { Branch, Repository } from '@scm-manager/ui-types';
|
||||
import FileTree from '../components/FileTree';
|
||||
import {
|
||||
BranchSelector,
|
||||
Breadcrumb,
|
||||
ErrorNotification,
|
||||
Loading
|
||||
} from "@scm-manager/ui-components";
|
||||
import { translate } from "react-i18next";
|
||||
Loading,
|
||||
} from '@scm-manager/ui-components';
|
||||
import { translate } from 'react-i18next';
|
||||
import {
|
||||
fetchBranches,
|
||||
getBranches,
|
||||
getFetchBranchesFailure,
|
||||
isFetchBranchesPending
|
||||
} from "../../branches/modules/branches";
|
||||
import { compose } from "redux";
|
||||
import Content from "./Content";
|
||||
import { fetchSources, isDirectory } from "../modules/sources";
|
||||
isFetchBranchesPending,
|
||||
} from '../../branches/modules/branches';
|
||||
import { compose } from 'redux';
|
||||
import Content from './Content';
|
||||
import { fetchSources, isDirectory } from '../modules/sources';
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
baseUrl: string,
|
||||
branches: Branch[],
|
||||
revision: string,
|
||||
path: string,
|
||||
currentFileIsDirectory: boolean,
|
||||
repository: Repository;
|
||||
loading: boolean;
|
||||
error: Error;
|
||||
baseUrl: string;
|
||||
branches: Branch[];
|
||||
revision: string;
|
||||
path: string;
|
||||
currentFileIsDirectory: boolean;
|
||||
|
||||
// dispatch props
|
||||
fetchBranches: Repository => void,
|
||||
fetchSources: (Repository, string, string) => void,
|
||||
fetchBranches: (p: Repository) => void;
|
||||
fetchSources: (p1: Repository, p2: string, p3: string) => void;
|
||||
|
||||
// Context props
|
||||
history: any,
|
||||
match: any,
|
||||
location: any,
|
||||
t: string => string
|
||||
history: any;
|
||||
match: any;
|
||||
location: any;
|
||||
t: (p: string) => string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
selectedBranch: any
|
||||
selectedBranch: any;
|
||||
};
|
||||
|
||||
class Sources extends React.Component<Props, State> {
|
||||
@@ -51,7 +50,7 @@ class Sources extends React.Component<Props, State> {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selectedBranch: null
|
||||
selectedBranch: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -61,7 +60,7 @@ class Sources extends React.Component<Props, State> {
|
||||
repository,
|
||||
revision,
|
||||
path,
|
||||
fetchSources
|
||||
fetchSources,
|
||||
} = this.props;
|
||||
|
||||
fetchBranches(repository);
|
||||
@@ -99,14 +98,18 @@ class Sources extends React.Component<Props, State> {
|
||||
const { baseUrl, history, path } = this.props;
|
||||
let url;
|
||||
if (branch) {
|
||||
this.setState({ selectedBranch: branch });
|
||||
this.setState({
|
||||
selectedBranch: branch,
|
||||
});
|
||||
if (path) {
|
||||
url = `${baseUrl}/${encodeURIComponent(branch.name)}/${path}`;
|
||||
} else {
|
||||
url = `${baseUrl}/${encodeURIComponent(branch.name)}/`;
|
||||
}
|
||||
} else {
|
||||
this.setState({ selectedBranch: null });
|
||||
this.setState({
|
||||
selectedBranch: null,
|
||||
});
|
||||
url = `${baseUrl}/`;
|
||||
}
|
||||
history.push(url);
|
||||
@@ -120,7 +123,7 @@ class Sources extends React.Component<Props, State> {
|
||||
error,
|
||||
revision,
|
||||
path,
|
||||
currentFileIsDirectory
|
||||
currentFileIsDirectory,
|
||||
} = this.props;
|
||||
|
||||
if (error) {
|
||||
@@ -160,7 +163,7 @@ class Sources extends React.Component<Props, State> {
|
||||
<BranchSelector
|
||||
branches={branches}
|
||||
selectedBranch={revision}
|
||||
label={t("changesets.branchSelectorLabel")}
|
||||
label={t('changesets.branchSelectorLabel')}
|
||||
selected={(b: Branch) => {
|
||||
this.branchSelected(b);
|
||||
}}
|
||||
@@ -212,7 +215,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
loading,
|
||||
error,
|
||||
branches,
|
||||
currentFileIsDirectory
|
||||
currentFileIsDirectory,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -223,15 +226,15 @@ const mapDispatchToProps = dispatch => {
|
||||
},
|
||||
fetchSources: (repository: Repository, revision: string, path: string) => {
|
||||
dispatch(fetchSources(repository, revision, path));
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default compose(
|
||||
translate("repos"),
|
||||
translate('repos'),
|
||||
withRouter,
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)
|
||||
mapDispatchToProps,
|
||||
),
|
||||
)(Sources);
|
||||
@@ -1,26 +1,25 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
|
||||
import SourcecodeViewer from "../components/content/SourcecodeViewer";
|
||||
import ImageViewer from "../components/content/ImageViewer";
|
||||
import DownloadViewer from "../components/content/DownloadViewer";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import { getContentType } from "./contentType";
|
||||
import type { File, Repository } from "@scm-manager/ui-types";
|
||||
import { ErrorNotification, Loading } from "@scm-manager/ui-components";
|
||||
import SourcecodeViewer from '../components/content/SourcecodeViewer';
|
||||
import ImageViewer from '../components/content/ImageViewer';
|
||||
import DownloadViewer from '../components/content/DownloadViewer';
|
||||
import { ExtensionPoint } from '@scm-manager/ui-extensions';
|
||||
import { getContentType } from './contentType';
|
||||
import { File, Repository } from '@scm-manager/ui-types';
|
||||
import { ErrorNotification, Loading } from '@scm-manager/ui-components';
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
file: File,
|
||||
revision: string,
|
||||
path: string
|
||||
repository: Repository;
|
||||
file: File;
|
||||
revision: string;
|
||||
path: string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
contentType: string,
|
||||
language: string,
|
||||
loaded: boolean,
|
||||
error?: Error
|
||||
contentType: string;
|
||||
language: string;
|
||||
loaded: boolean;
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
class SourcesView extends React.Component<Props, State> {
|
||||
@@ -28,9 +27,9 @@ class SourcesView extends React.Component<Props, State> {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
contentType: "",
|
||||
language: "",
|
||||
loaded: false
|
||||
contentType: '',
|
||||
language: '',
|
||||
loaded: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,14 +41,14 @@ class SourcesView extends React.Component<Props, State> {
|
||||
this.setState({
|
||||
...this.state,
|
||||
error: result.error,
|
||||
loaded: true
|
||||
loaded: true,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
...this.state,
|
||||
contentType: result.type,
|
||||
language: result.language,
|
||||
loaded: true
|
||||
loaded: true,
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -59,17 +58,21 @@ class SourcesView extends React.Component<Props, State> {
|
||||
showSources() {
|
||||
const { file, revision } = this.props;
|
||||
const { contentType, language } = this.state;
|
||||
if (contentType.startsWith("image/")) {
|
||||
if (contentType.startsWith('image/')) {
|
||||
return <ImageViewer file={file} />;
|
||||
} else if (language) {
|
||||
return <SourcecodeViewer file={file} language={language} />;
|
||||
} else if (contentType.startsWith("text/")) {
|
||||
} else if (contentType.startsWith('text/')) {
|
||||
return <SourcecodeViewer file={file} language="none" />;
|
||||
} else {
|
||||
return (
|
||||
<ExtensionPoint
|
||||
name="repos.sources.view"
|
||||
props={{ file, contentType, revision }}
|
||||
props={{
|
||||
file,
|
||||
contentType,
|
||||
revision,
|
||||
}}
|
||||
>
|
||||
<DownloadViewer file={file} />
|
||||
</ExtensionPoint>
|
||||
@@ -1,16 +0,0 @@
|
||||
//@flow
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
|
||||
export function getContentType(url: string) {
|
||||
return apiClient
|
||||
.head(url)
|
||||
.then(response => {
|
||||
return {
|
||||
type: response.headers.get("Content-Type"),
|
||||
language: response.headers.get("X-Programming-Language")
|
||||
};
|
||||
})
|
||||
.catch(err => {
|
||||
return { error: err };
|
||||
});
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
//@flow
|
||||
import fetchMock from "fetch-mock";
|
||||
import { getContentType } from "./contentType";
|
||||
|
||||
describe("get content type", () => {
|
||||
const CONTENT_URL = "/repositories/scmadmin/TestRepo/content/testContent";
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it("should return content", done => {
|
||||
let headers = {
|
||||
"Content-Type": "application/text",
|
||||
"X-Programming-Language": "JAVA"
|
||||
};
|
||||
|
||||
fetchMock.head("/api/v2" + CONTENT_URL, {
|
||||
headers
|
||||
});
|
||||
|
||||
getContentType(CONTENT_URL).then(content => {
|
||||
expect(content.type).toBe("application/text");
|
||||
expect(content.language).toBe("JAVA");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { getContentType } from './contentType';
|
||||
|
||||
describe('get content type', () => {
|
||||
const CONTENT_URL = '/repositories/scmadmin/TestRepo/content/testContent';
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it('should return content', done => {
|
||||
let headers = {
|
||||
'Content-Type': 'application/text',
|
||||
'X-Programming-Language': 'JAVA',
|
||||
};
|
||||
|
||||
fetchMock.head('/api/v2' + CONTENT_URL, {
|
||||
headers,
|
||||
});
|
||||
|
||||
getContentType(CONTENT_URL).then(content => {
|
||||
expect(content.type).toBe('application/text');
|
||||
expect(content.language).toBe('JAVA');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
17
scm-ui/ui-webapp/src/repos/sources/containers/contentType.ts
Normal file
17
scm-ui/ui-webapp/src/repos/sources/containers/contentType.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { apiClient } from '@scm-manager/ui-components';
|
||||
|
||||
export function getContentType(url: string) {
|
||||
return apiClient
|
||||
.head(url)
|
||||
.then(response => {
|
||||
return {
|
||||
type: response.headers.get('Content-Type'),
|
||||
language: response.headers.get('X-Programming-Language'),
|
||||
};
|
||||
})
|
||||
.catch(err => {
|
||||
return {
|
||||
error: err,
|
||||
};
|
||||
});
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user