implemented git repository type configuration

This commit is contained in:
Sebastian Sdorra
2018-11-06 07:59:34 +01:00
parent ef23178fad
commit dd95c9c306
22 changed files with 321 additions and 1368 deletions

View File

@@ -12,6 +12,6 @@
"@scm-manager/ui-extensions": "^0.0.7" "@scm-manager/ui-extensions": "^0.0.7"
}, },
"devDependencies": { "devDependencies": {
"@scm-manager/ui-bundler": "^0.0.19" "@scm-manager/ui-bundler": "^0.0.21"
} }
} }

View File

@@ -116,17 +116,17 @@ public class GitRepositoryHandler
public void init(SCMContextProvider context) public void init(SCMContextProvider context)
{ {
super.init(context); super.init(context);
scheduleGc(); scheduleGc(getConfig().getGcExpression());
} }
@Override @Override
public void setConfig(GitConfig config) public void setConfig(GitConfig config)
{ {
scheduleGc(config.getGcExpression());
super.setConfig(config); super.setConfig(config);
scheduleGc();
} }
private void scheduleGc() private void scheduleGc(String expression)
{ {
synchronized (LOCK){ synchronized (LOCK){
if ( task != null ){ if ( task != null ){
@@ -134,11 +134,10 @@ public class GitRepositoryHandler
task.cancel(); task.cancel();
task = null; task = null;
} }
String exp = getConfig().getGcExpression(); if (!Strings.isNullOrEmpty(expression))
if (!Strings.isNullOrEmpty(exp))
{ {
logger.info("schedule git gc task with expression {}", exp); logger.info("schedule git gc task with expression {}", expression);
task = scheduler.schedule(exp, GitGcTask.class); task = scheduler.schedule(expression, GitGcTask.class);
} }
} }
} }

View File

@@ -0,0 +1,183 @@
//@flow
import React from "react";
import { translate } from "react-i18next";
import type { Links } from "@scm-manager/ui-types";
import {
apiClient,
Title,
InputField,
Checkbox,
SubmitButton,
Loading,
ErrorNotification
} from "@scm-manager/ui-components";
type Props = {
url: string,
// context props
t: (string) => string
};
type Configuration = {
repositoryDirectory?: string,
gcExpression?: string,
disabled: boolean,
_links?: Links
}
type State = Configuration & {
error?: Error,
fetching: boolean,
modifying: boolean
};
class GitConfiguration extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
disabled: false,
fetching: true,
modifying: false
};
}
componentDidMount() {
const { url } = this.props;
// TODO capture content-type for sending
apiClient.get(url)
.then(response => response.json())
.then(this.loadConfig)
.catch(this.handleError);
}
handleError = (error: Error) => {
this.setState({
error,
fetching: false,
modifying: false
});
};
loadConfig = (configuration: Configuration) => {
this.setState({
...configuration,
fetching: false,
error: undefined
});
};
handleInputChange = (value: string, name: string) => {
this.setState({
[name]: value
});
};
handleCheckboxChange = (value: boolean, name: string) => {
this.setState({
[name]: value
});
};
isValid = (): boolean => {
const { repositoryDirectory } = this.state;
return !!repositoryDirectory;
};
getModificationUrl = (): ?string => {
const links = this.state._links;
if (links && links.update) {
return links.update.href;
}
};
isReadOnly = (): boolean => {
const links = this.state._links;
return !links || !links.update;
};
render() {
const { fetching, error } = this.state;
if (error) {
return this.renderWithFrame(<ErrorNotification error={error}/>);
} else if (fetching) {
return this.renderWithFrame(<Loading/>);
}
return this.renderForm();
}
renderWithFrame(child) {
const { t } = this.props;
return (
<div>
<Title title={t("scm-git-plugin.config.title")}/>
{child}
</div>
);
}
modifyConfiguration = (event: Event) => {
event.preventDefault();
this.setState({ modifying: true });
const { repositoryDirectory, gcExpression, disabled } = this.state;
const configuration = {
repositoryDirectory,
gcExpression,
disabled
};
apiClient.put(this.getModificationUrl(), configuration, "application/vnd.scmm-gitconfig+json;v=2")
.then(() => this.setState({ modifying: false }))
.catch(this.handleError);
};
renderForm() {
const { repositoryDirectory, gcExpression, disabled, modifying } = this.state;
const { t } = this.props;
const readOnly = this.isReadOnly();
return this.renderWithFrame(
<form onSubmit={this.modifyConfiguration}>
<InputField name="repositoryDirectory"
label={t("scm-git-plugin.config.directory")}
helpText={t("scm-git-plugin.config.directoryHelpText")}
value={repositoryDirectory}
onChange={this.handleInputChange}
disabled={readOnly}
/>
<InputField name="gcExpression"
label={t("scm-git-plugin.config.gcExpression")}
helpText={t("scm-git-plugin.config.gcExpressionHelpText")}
value={gcExpression}
onChange={this.handleInputChange}
disabled={readOnly}
/>
<Checkbox name="disabled"
label={t("scm-git-plugin.config.disabled")}
helpText={t("scm-git-plugin.config.disabledHelpText")}
checked={disabled}
onChange={this.handleCheckboxChange}
disabled={readOnly}
/>
<hr/>
<SubmitButton
label={t("scm-git-plugin.config.submit")}
disabled={!this.isValid() || readOnly}
loading={modifying}
/>
</form>
);
}
}
export default translate("plugins")(GitConfiguration);

View File

@@ -0,0 +1,21 @@
//@flow
import React from "react";
import { NavLink } from "@scm-manager/ui-components";
import { translate } from "react-i18next";
type Props = {
url: string,
// context props
t: (string) => string
}
class GitConfigurationNavLink extends React.Component<Props> {
render() {
const { url, t } = this.props;
return <NavLink to={`${url}/git`} label={t("scm-git-plugin.config.link")} />;
}
}
export default translate("plugins")(GitConfigurationNavLink);

View File

@@ -0,0 +1,25 @@
//@flow
import React from "react";
import type { Links } from "@scm-manager/ui-types";
import GitConfiguration from "./GitConfiguration";
import { Route } from "react-router-dom";
type Props = {
url: string,
links: Links
}
class GitConfigurationRoute extends React.Component<Props> {
render() {
const { url, links } = this.props;
const configLink = links["gitConfig"].href;
return <Route path={url + "/git"}
component={() => <GitConfiguration url={configLink} />}
exact />;
}
}
export default GitConfigurationRoute;

View File

@@ -2,6 +2,10 @@
import { binder } from "@scm-manager/ui-extensions"; import { binder } from "@scm-manager/ui-extensions";
import ProtocolInformation from "./ProtocolInformation"; import ProtocolInformation from "./ProtocolInformation";
import GitAvatar from "./GitAvatar"; import GitAvatar from "./GitAvatar";
import GitConfigurationNavLink from "./GitConfigurationNavLink";
import GitConfigurationRoute from "./GitConfigurationRoute";
// repository
const gitPredicate = (props: Object) => { const gitPredicate = (props: Object) => {
return props.repository && props.repository.type === "git"; return props.repository && props.repository.type === "git";
@@ -9,3 +13,12 @@ const gitPredicate = (props: Object) => {
binder.bind("repos.repository-details.information", ProtocolInformation, gitPredicate); binder.bind("repos.repository-details.information", ProtocolInformation, gitPredicate);
binder.bind("repos.repository-avatar", GitAvatar, gitPredicate); binder.bind("repos.repository-avatar", GitAvatar, gitPredicate);
// global config
const gitConfigPredicate = (props: Object) => {
return props.links && props.links["gitConfig"];
};
binder.bind("config.navigation", GitConfigurationNavLink, gitConfigPredicate);
binder.bind("config.route", GitConfigurationRoute, gitConfigPredicate);

View File

@@ -4,6 +4,17 @@
"clone" : "Clone the repository", "clone" : "Clone the repository",
"create" : "Create a new repository", "create" : "Create a new repository",
"replace" : "Push an existing repository" "replace" : "Push an existing repository"
},
"config": {
"link": "Git",
"title": "Git Configuration",
"directory": "Repository Directory",
"directoryHelpText": "Location of the Git repositories.",
"gcExpression": "GC Cron Expression",
"gcExpressionHelpText": "Use Quartz Cron Expressions (SECOND MINUTE HOUR DAYOFMONTH MONTH DAYOFWEEK) to run git gc in intervals.",
"disabled": "Disabled",
"disabledHelpText": "Enable or disable the Git plugin",
"submit": "Submit"
} }
} }
} }

View File

@@ -707,9 +707,9 @@
version "0.0.2" version "0.0.2"
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
"@scm-manager/ui-bundler@^0.0.19": "@scm-manager/ui-bundler@^0.0.21":
version "0.0.19" version "0.0.21"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.19.tgz#646ab1fa1e5389fad614542215c60678fb9816ae" resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.21.tgz#f8b5fa355415cc67b8aaf8744e1701a299dff647"
dependencies: dependencies:
"@babel/core" "^7.0.0" "@babel/core" "^7.0.0"
"@babel/plugin-proposal-class-properties" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0"

View File

@@ -9,6 +9,6 @@
"@scm-manager/ui-extensions": "^0.0.7" "@scm-manager/ui-extensions": "^0.0.7"
}, },
"devDependencies": { "devDependencies": {
"@scm-manager/ui-bundler": "^0.0.19" "@scm-manager/ui-bundler": "^0.0.21"
} }
} }

View File

@@ -641,9 +641,9 @@
version "0.0.2" version "0.0.2"
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
"@scm-manager/ui-bundler@^0.0.19": "@scm-manager/ui-bundler@^0.0.21":
version "0.0.19" version "0.0.21"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.19.tgz#646ab1fa1e5389fad614542215c60678fb9816ae" resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.21.tgz#f8b5fa355415cc67b8aaf8744e1701a299dff647"
dependencies: dependencies:
"@babel/core" "^7.0.0" "@babel/core" "^7.0.0"
"@babel/plugin-proposal-class-properties" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0"
@@ -660,7 +660,6 @@
browserify-css "^0.14.0" browserify-css "^0.14.0"
colors "^1.3.1" colors "^1.3.1"
commander "^2.17.1" commander "^2.17.1"
connect-history-api-fallback "^1.5.0"
eslint "^5.4.0" eslint "^5.4.0"
eslint-config-react-app "^2.1.0" eslint-config-react-app "^2.1.0"
eslint-plugin-flowtype "^2.50.0" eslint-plugin-flowtype "^2.50.0"

View File

@@ -9,6 +9,6 @@
"@scm-manager/ui-extensions": "^0.0.7" "@scm-manager/ui-extensions": "^0.0.7"
}, },
"devDependencies": { "devDependencies": {
"@scm-manager/ui-bundler": "^0.0.19" "@scm-manager/ui-bundler": "^0.0.21"
} }
} }

View File

@@ -641,9 +641,9 @@
version "0.0.2" version "0.0.2"
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
"@scm-manager/ui-bundler@^0.0.19": "@scm-manager/ui-bundler@^0.0.21":
version "0.0.19" version "0.0.21"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.19.tgz#646ab1fa1e5389fad614542215c60678fb9816ae" resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.21.tgz#f8b5fa355415cc67b8aaf8744e1701a299dff647"
dependencies: dependencies:
"@babel/core" "^7.0.0" "@babel/core" "^7.0.0"
"@babel/plugin-proposal-class-properties" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0"

View File

@@ -12,7 +12,7 @@
"eslint-fix": "eslint src --fix" "eslint-fix": "eslint src --fix"
}, },
"devDependencies": { "devDependencies": {
"@scm-manager/ui-bundler": "^0.0.19", "@scm-manager/ui-bundler": "^0.0.21",
"create-index": "^2.3.0", "create-index": "^2.3.0",
"enzyme": "^3.5.0", "enzyme": "^3.5.0",
"enzyme-adapter-react-16": "^1.3.1", "enzyme-adapter-react-16": "^1.3.1",

View File

@@ -4,15 +4,16 @@ import { Help } from "../index";
type Props = { type Props = {
label?: string, label?: string,
name?: string,
checked: boolean, checked: boolean,
onChange?: boolean => void, onChange?: (value: boolean, name?: string) => void,
disabled?: boolean, disabled?: boolean,
helpText?: string helpText?: string
}; };
class Checkbox extends React.Component<Props> { class Checkbox extends React.Component<Props> {
onCheckboxChange = (event: SyntheticInputEvent<HTMLInputElement>) => { onCheckboxChange = (event: SyntheticInputEvent<HTMLInputElement>) => {
if (this.props.onChange) { if (this.props.onChange) {
this.props.onChange(event.target.checked); this.props.onChange(event.target.checked, this.props.name);
} }
}; };

View File

@@ -5,11 +5,12 @@ import { LabelWithHelpIcon } from "../index";
type Props = { type Props = {
label?: string, label?: string,
name?: string,
placeholder?: string, placeholder?: string,
value?: string, value?: string,
type?: string, type?: string,
autofocus?: boolean, autofocus?: boolean,
onChange: string => void, onChange: (value: string, name?: string) => void,
onReturnPressed?: () => void, onReturnPressed?: () => void,
validationError: boolean, validationError: boolean,
errorMessage: string, errorMessage: string,
@@ -32,7 +33,7 @@ class InputField extends React.Component<Props> {
} }
handleInput = (event: SyntheticInputEvent<HTMLInputElement>) => { handleInput = (event: SyntheticInputEvent<HTMLInputElement>) => {
this.props.onChange(event.target.value); this.props.onChange(event.target.value, this.props.name);
}; };
handleKeyPress = (event: SyntheticKeyboardEvent<HTMLInputElement>) => { handleKeyPress = (event: SyntheticKeyboardEvent<HTMLInputElement>) => {

View File

@@ -641,9 +641,9 @@
version "0.0.2" version "0.0.2"
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
"@scm-manager/ui-bundler@^0.0.19": "@scm-manager/ui-bundler@^0.0.21":
version "0.0.19" version "0.0.21"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.19.tgz#646ab1fa1e5389fad614542215c60678fb9816ae" resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.21.tgz#f8b5fa355415cc67b8aaf8744e1701a299dff647"
dependencies: dependencies:
"@babel/core" "^7.0.0" "@babel/core" "^7.0.0"
"@babel/plugin-proposal-class-properties" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0"

View File

@@ -14,7 +14,7 @@
"check": "flow check" "check": "flow check"
}, },
"devDependencies": { "devDependencies": {
"@scm-manager/ui-bundler": "^0.0.19" "@scm-manager/ui-bundler": "^0.0.21"
}, },
"browserify": { "browserify": {
"transform": [ "transform": [

View File

@@ -707,9 +707,9 @@
version "0.0.2" version "0.0.2"
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
"@scm-manager/ui-bundler@^0.0.19": "@scm-manager/ui-bundler@^0.0.21":
version "0.0.19" version "0.0.21"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.19.tgz#646ab1fa1e5389fad614542215c60678fb9816ae" resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.21.tgz#f8b5fa355415cc67b8aaf8744e1701a299dff647"
dependencies: dependencies:
"@babel/core" "^7.0.0" "@babel/core" "^7.0.0"
"@babel/plugin-proposal-class-properties" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0"

View File

@@ -48,7 +48,7 @@
"pre-commit": "jest && flow && eslint src" "pre-commit": "jest && flow && eslint src"
}, },
"devDependencies": { "devDependencies": {
"@scm-manager/ui-bundler": "^0.0.19", "@scm-manager/ui-bundler": "^0.0.21",
"copyfiles": "^2.0.0", "copyfiles": "^2.0.0",
"enzyme": "^3.3.0", "enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1", "enzyme-adapter-react-16": "^1.1.1",

View File

@@ -2,12 +2,19 @@
import React from "react"; import React from "react";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import { Route } from "react-router"; import { Route } from "react-router";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import type { Links } from "@scm-manager/ui-types";
import { Page, Navigation, NavLink, Section } from "@scm-manager/ui-components"; import { Page, Navigation, NavLink, Section } from "@scm-manager/ui-components";
import GlobalConfig from "./GlobalConfig"; import GlobalConfig from "./GlobalConfig";
import type { History } from "history"; import type { History } from "history";
import {connect} from "react-redux";
import {compose} from "redux";
import { getLinks } from "../../modules/indexResource";
type Props = { type Props = {
links: Links,
// context objects // context objects
t: string => string, t: string => string,
match: any, match: any,
@@ -27,15 +34,23 @@ class Config extends React.Component<Props> {
}; };
render() { render() {
const { t } = this.props; const { links, t } = this.props;
const url = this.matchedUrl(); const url = this.matchedUrl();
const extensionProps = {
links,
url
};
return ( return (
<Page> <Page>
<div className="columns"> <div className="columns">
<div className="column is-three-quarters"> <div className="column is-three-quarters">
<Route path={url} exact component={GlobalConfig} /> <Route path={url} exact component={GlobalConfig} />
<ExtensionPoint name="config.route"
props={extensionProps}
renderAll={true}
/>
</div> </div>
<div className="column"> <div className="column">
<Navigation> <Navigation>
@@ -44,6 +59,10 @@ class Config extends React.Component<Props> {
to={`${url}`} to={`${url}`}
label={t("global-config.navigation-label")} label={t("global-config.navigation-label")}
/> />
<ExtensionPoint name="config.navigation"
props={extensionProps}
renderAll={true}
/>
</Section> </Section>
</Navigation> </Navigation>
</div> </div>
@@ -53,4 +72,15 @@ class Config extends React.Component<Props> {
} }
} }
export default translate("config")(Config); const mapStateToProps = (state: any) => {
const links = getLinks(state);
return {
links
};
};
export default compose(
connect(mapStateToProps),
translate("config")
)(Config);

View File

@@ -101,7 +101,6 @@ class Main extends React.Component<Props> {
authenticated={authenticated} authenticated={authenticated}
/> />
<ProtectedRoute <ProtectedRoute
exact
path="/config" path="/config"
component={Config} component={Config}
authenticated={authenticated} authenticated={authenticated}

File diff suppressed because it is too large Load Diff