import { omitBy } from 'lodash';
import { getJSON, RequestData, post, postJSON } from '../helpers/request';
import { TYPE_PROPERTY_SET } from '../apps/settings/constants';
+import throwGlobalError from '../app/utils/throwGlobalError';
export function getDefinitions(component: string | null, branch?: string): Promise<any> {
return getJSON('/api/settings/list_definitions', { branch, component }).then(r => r.definitions);
}
-export function getValues(keys: string, component?: string, branch?: string): Promise<any> {
+export interface SettingValue {
+ inherited?: boolean;
+ key: string;
+ parentValue?: string;
+ parentValues?: string[];
+ value?: any;
+ values?: string[];
+}
+
+export function getValues(
+ keys: string,
+ component?: string,
+ branch?: string
+): Promise<SettingValue[]> {
return getJSON('/api/settings/values', { keys, component, branch }).then(r => r.settings);
}
return post('/api/settings/set', data);
}
+export function setSimpleSettingValue(parameters: {
+ branch?: string;
+ component?: string;
+ value: string;
+ key: string;
+}): Promise<void | Response> {
+ return post('/api/settings/set', parameters).catch(throwGlobalError);
+}
+
export function resetSettingValue(key: string, component?: string, branch?: string): Promise<void> {
return post('/api/settings/reset', { keys: key, component, branch });
}
renderAdministration() {
const { branch } = this.props;
- if (!this.getConfiguration().showSettings || (branch && isShortLivingBranch(branch))) {
+ if (!this.getConfiguration().showSettings || (branch && !branch.isMain)) {
return null;
}
project_activity.page
</Link>
</li>
- <li>
- <Link
- className="is-admin"
- id="component-navigation-admin"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/project/settings",
- "query": Object {
- "branch": "release",
- "id": "foo",
- },
- }
- }
- >
- branches.branch_settings
- </Link>
- </li>
</NavBarTabs>
`;
}
export interface MainBranch {
+ analysisDate?: string;
isMain: true;
name: string;
status?: {
}
export interface LongLivingBranch {
+ analysisDate?: string;
isMain: false;
name: string;
status?: {
}
export interface ShortLivingBranch {
+ analysisDate?: string;
isMain: false;
isOrphan?: true;
mergeBranch: string;
import { formatMeasure } from '../../../helpers/measures';
import RemoveMemberForm from './forms/RemoveMemberForm';
import ManageMemberGroupsForm from './forms/ManageMemberGroupsForm';
+import SettingsIcon from '../../../components/icons-components/SettingsIcon';
/*:: import type { Member } from '../../../store/organizationsMembers/actions'; */
/*:: import type { Organization, OrgGroup } from '../../../store/organizations/duck'; */
<button
className="dropdown-toggle little-spacer-right button-compact"
data-toggle="dropdown">
- <i className="icon-settings" /> <i className="icon-dropdown" />
+ <SettingsIcon style={{ marginTop: 4 }} /> <i className="icon-dropdown" />
</button>
<ul className="dropdown-menu dropdown-menu-right">
<li>
className="dropdown-toggle little-spacer-right button-compact"
data-toggle="dropdown"
>
- <i
- className="icon-settings"
+ <SettingsIcon
+ style={
+ Object {
+ "marginTop": 4,
+ }
+ }
/>
<i
className="dropdown-toggle little-spacer-right button-compact"
data-toggle="dropdown"
>
- <i
- className="icon-settings"
+ <SettingsIcon
+ style={
+ Object {
+ "marginTop": 4,
+ }
+ }
/>
<i
import AddEventForm from './forms/AddEventForm';
import RemoveAnalysisForm from './forms/RemoveAnalysisForm';
import TimeTooltipFormatter from '../../../components/intl/TimeTooltipFormatter';
+import SettingsIcon from '../../../components/icons-components/SettingsIcon';
import { translate } from '../../../helpers/l10n';
/*:: import type { Analysis } from '../types'; */
className="js-analysis-actions button-small button-compact dropdown-toggle"
data-toggle="dropdown"
onClick={this.stopPropagation}>
- <i className="icon-settings" /> <i className="icon-dropdown" />
+ <SettingsIcon size={12} style={{ marginTop: 3 }} /> <i className="icon-dropdown" />
</button>
<ul className="dropdown-menu dropdown-menu-right">
{!hasVersion &&
*/
import * as React from 'react';
import BranchRow from './BranchRow';
+import LongBranchesPattern from './LongBranchesPattern';
import { Branch } from '../../../app/types';
import { sortBranchesAsTree } from '../../../helpers/branches';
import { translate } from '../../../helpers/l10n';
+import { getValues } from '../../../api/settings';
+import { FormattedMessage } from 'react-intl';
+import { formatMeasure } from '../../../helpers/measures';
+import { Link } from 'react-router';
interface Props {
branches: Branch[];
+ canAdmin?: boolean;
component: { key: string };
onBranchesChange: () => void;
}
-export default function App({ branches, component, onBranchesChange }: Props) {
- return (
- <div className="page page-limited">
- <header className="page-header">
- <h1 className="page-title">{translate('project_branches.page')}</h1>
- </header>
-
- <table className="data zebra zebra-hover">
- <thead>
- <tr>
- <th>{translate('branch')}</th>
- <th className="text-right">{translate('status')}</th>
- <th className="text-right">{translate('actions')}</th>
- </tr>
- </thead>
- <tbody>
- {sortBranchesAsTree(branches).map(branch => (
- <BranchRow
- branch={branch}
- component={component.key}
- key={branch.name}
- onChange={onBranchesChange}
- />
- ))}
- </tbody>
- </table>
- </div>
- );
+interface State {
+ branchLifeTime?: string;
+ loading: boolean;
+}
+
+const BRANCH_LIFETIME_SETTING = 'sonar.dbcleaner.daysBeforeDeletingInactiveShortLivingBranches';
+
+export default class App extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { loading: true };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchPurgeSetting();
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchPurgeSetting() {
+ this.setState({ loading: true });
+ getValues(BRANCH_LIFETIME_SETTING).then(
+ settings => {
+ if (this.mounted) {
+ this.setState({
+ loading: false,
+ branchLifeTime: settings.length > 0 ? settings[0].value : undefined
+ });
+ }
+ },
+ () => {
+ this.setState({ loading: false });
+ }
+ );
+ }
+
+ renderBranchLifeTime() {
+ const { branchLifeTime } = this.state;
+ if (!branchLifeTime) {
+ return null;
+ }
+
+ const messageKey = this.props.canAdmin
+ ? 'project_branches.page.life_time.admin'
+ : 'project_branches.page.life_time';
+
+ return (
+ <p className="page-description">
+ <FormattedMessage
+ defaultMessage={translate(messageKey)}
+ id={messageKey}
+ values={{
+ days: formatMeasure(this.state.branchLifeTime, 'INT'),
+ settings: <Link to="/admin/settings">{translate('settings.page')}</Link>
+ }}
+ />
+ </p>
+ );
+ }
+
+ render() {
+ const { branches, component, onBranchesChange } = this.props;
+
+ if (this.state.loading) {
+ return (
+ <div className="page page-limited">
+ <header className="page-header">
+ <h1 className="page-title">{translate('project_branches.page')}</h1>
+ </header>
+ <i className="spinner" />
+ </div>
+ );
+ }
+
+ return (
+ <div className="page page-limited">
+ <header className="page-header">
+ <h1 className="page-title">{translate('project_branches.page')}</h1>
+ <LongBranchesPattern project={component.key} />
+ <p className="page-description">{translate('project_branches.page.description')}</p>
+ {this.renderBranchLifeTime()}
+ </header>
+
+ <table className="data zebra zebra-hover">
+ <thead>
+ <tr>
+ <th>{translate('branch')}</th>
+ <th className="thin nowrap text-right">{translate('status')}</th>
+ <th className="thin nowrap text-right">
+ {translate('project_history.last_snapshot')}
+ </th>
+ <th className="thin nowrap text-right">{translate('actions')}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {sortBranchesAsTree(branches).map(branch => (
+ <BranchRow
+ branch={branch}
+ component={component.key}
+ key={branch.name}
+ onChange={onBranchesChange}
+ />
+ ))}
+ </tbody>
+ </table>
+ </div>
+ );
+ }
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { connect } from 'react-redux';
+import App from './App';
+import { getAppState } from '../../../store/rootReducer';
+
+const mapStateToProps = (state: any) => ({
+ canAdmin: getAppState(state).canAdmin
+});
+
+export default connect<any, any, any>(mapStateToProps)(App);
import { Branch } from '../../../app/types';
import * as classNames from 'classnames';
import DeleteBranchModal from './DeleteBranchModal';
+import LeakPeriodForm from './LeakPeriodForm';
import BranchStatus from '../../../components/common/BranchStatus';
import BranchIcon from '../../../components/icons-components/BranchIcon';
-import { isShortLivingBranch } from '../../../helpers/branches';
-import ChangeIcon from '../../../components/icons-components/ChangeIcon';
-import DeleteIcon from '../../../components/icons-components/DeleteIcon';
+import { isShortLivingBranch, isLongLivingBranch } from '../../../helpers/branches';
import { translate } from '../../../helpers/l10n';
-import Tooltip from '../../../components/controls/Tooltip';
import RenameBranchModal from './RenameBranchModal';
+import DateFromNow from '../../../components/intl/DateFromNow';
+import SettingsIcon from '../../../components/icons-components/SettingsIcon';
interface Props {
branch: Branch;
}
interface State {
+ changingLeak: boolean;
deleting: boolean;
renaming: boolean;
}
export default class BranchRow extends React.PureComponent<Props, State> {
mounted: boolean;
- state: State = { deleting: false, renaming: false };
+ state: State = { changingLeak: false, deleting: false, renaming: false };
componentDidMount() {
this.mounted = true;
this.setState({ renaming: false });
};
+ handleChangeLeakClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.setState({ changingLeak: true });
+ };
+
+ handleChangingLeakStop = () => {
+ if (this.mounted) {
+ this.setState({ changingLeak: false });
+ }
+ };
+
render() {
const { branch, component } = this.props;
<BranchStatus branch={branch} />
</td>
<td className="thin nowrap text-right">
- {branch.isMain ? (
- <Tooltip overlay={translate('branches.rename')}>
- <a className="js-rename link-no-underline" href="#" onClick={this.handleRenameClick}>
- <ChangeIcon />
- </a>
- </Tooltip>
- ) : (
- <Tooltip overlay={translate('branches.delete')}>
- <a className="js-delete link-no-underline" href="#" onClick={this.handleDeleteClick}>
- <DeleteIcon />
- </a>
- </Tooltip>
- )}
+ {branch.analysisDate && <DateFromNow date={branch.analysisDate} />}
+ </td>
+ <td className="thin nowrap text-right">
+ <div className="dropdown big-spacer-left">
+ <button
+ className="dropdown-toggle little-spacer-right button-compact"
+ data-toggle="dropdown">
+ <SettingsIcon style={{ marginTop: 4 }} /> <i className="icon-dropdown" />
+ </button>
+ <ul className="dropdown-menu dropdown-menu-right">
+ {isLongLivingBranch(branch) && (
+ <li>
+ <a
+ className="js-change-leak-period link-no-underline"
+ href="#"
+ onClick={this.handleChangeLeakClick}>
+ {translate('branches.set_leak_period')}
+ </a>
+ </li>
+ )}
+ {branch.isMain ? (
+ <li>
+ <a
+ className="js-rename link-no-underline"
+ href="#"
+ onClick={this.handleRenameClick}>
+ {translate('branches.rename')}
+ </a>
+ </li>
+ ) : (
+ <li>
+ <a
+ className="js-delete link-no-underline"
+ href="#"
+ onClick={this.handleDeleteClick}>
+ {translate('branches.delete')}
+ </a>
+ </li>
+ )}
+ </ul>
+ </div>
</td>
{this.state.deleting && (
onRename={this.handleChange}
/>
)}
+
+ {this.state.changingLeak && (
+ <LeakPeriodForm
+ branch={branch.name}
+ onClose={this.handleChangingLeakStop}
+ project={component}
+ />
+ )}
</tr>
);
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import Modal from 'react-modal';
+import SettingForm from './SettingForm';
+import { translate } from '../../../helpers/l10n';
+import { getValues, SettingValue } from '../../../api/settings';
+
+interface Props {
+ branch: string;
+ onClose: () => void;
+ project: string;
+}
+
+interface State {
+ loading: boolean;
+ setting?: SettingValue;
+ submitting: boolean;
+ value?: string;
+}
+
+const LEAK_PERIOD = 'sonar.leak.period';
+
+export default class LeakPeriodForm extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { loading: true, submitting: false };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchSetting();
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchSetting() {
+ this.setState({ loading: true });
+ getValues(LEAK_PERIOD, this.props.project, this.props.branch).then(
+ settings => {
+ if (this.mounted) {
+ this.setState({ loading: false, setting: settings[0] });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ }
+
+ handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ this.props.onClose();
+ };
+
+ render() {
+ const { setting } = this.state;
+ const header = translate('branches.set_leak_period');
+
+ return (
+ <Modal
+ isOpen={true}
+ contentLabel={header}
+ className="modal"
+ overlayClassName="modal-overlay"
+ onRequestClose={this.props.onClose}>
+ <header className="modal-head">
+ <h2>{header}</h2>
+ </header>
+ {this.state.loading && (
+ <div className="modal-body">
+ <i className="spinner" />
+ </div>
+ )}
+ {setting && (
+ <SettingForm
+ branch={this.props.branch}
+ onChange={this.props.onClose}
+ onClose={this.props.onClose}
+ project={this.props.project}
+ setting={setting}
+ />
+ )}
+ </Modal>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import LongBranchesPatternForm from './LongBranchesPatternForm';
+import { getValues, SettingValue } from '../../../api/settings';
+import ChangeIcon from '../../../components/icons-components/ChangeIcon';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ project: string;
+}
+
+interface State {
+ formOpen: boolean;
+ setting?: SettingValue;
+}
+
+export const LONG_BRANCH_PATTERN = 'sonar.branch.longLivedBranches.regex';
+
+export default class LongBranchesPattern extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { formOpen: false };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchSetting();
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchSetting() {
+ return getValues(LONG_BRANCH_PATTERN, this.props.project).then(
+ settings => {
+ if (this.mounted) {
+ this.setState({ setting: settings[0] });
+ }
+ },
+ () => {}
+ );
+ }
+
+ closeForm = () => {
+ if (this.mounted) {
+ this.setState({ formOpen: false });
+ }
+ };
+
+ handleChangeClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.setState({ formOpen: true });
+ };
+
+ handleChange = () => {
+ if (this.mounted) {
+ this.fetchSetting().then(this.closeForm, this.closeForm);
+ }
+ };
+
+ render() {
+ const { setting } = this.state;
+
+ if (!setting) {
+ return null;
+ }
+
+ return (
+ <div className="pull-right text-right">
+ {translate('branches.long_living_branches_pattern')}
+ {': '}
+ <strong>{setting.value}</strong>
+ <a
+ className="display-inline-block spacer-left link-no-underline"
+ href="#"
+ onClick={this.handleChangeClick}>
+ <ChangeIcon />
+ </a>
+ {this.state.formOpen && (
+ <LongBranchesPatternForm
+ onClose={this.closeForm}
+ onChange={this.handleChange}
+ project={this.props.project}
+ setting={setting}
+ />
+ )}
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import Modal from 'react-modal';
+import SettingForm from './SettingForm';
+import { translate } from '../../../helpers/l10n';
+import { SettingValue } from '../../../api/settings';
+
+interface Props {
+ onChange: () => void;
+ onClose: () => void;
+ project: string;
+ setting: SettingValue;
+}
+
+export default function LongBranchesPatternForm(props: Props) {
+ const header = translate('branches.detection_of_long_living_branches');
+
+ return (
+ <Modal
+ isOpen={true}
+ contentLabel={header}
+ className="modal"
+ overlayClassName="modal-overlay"
+ onRequestClose={props.onClose}>
+ <header className="modal-head">
+ <h2>{header}</h2>
+ </header>
+ <SettingForm {...props} />
+ </Modal>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { SettingValue, setSimpleSettingValue, resetSettingValue } from '../../../api/settings';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+
+interface Props {
+ branch?: string;
+ onClose: () => void;
+ onChange: () => void;
+ project: string;
+ setting: SettingValue;
+}
+
+interface State {
+ submitting: boolean;
+ value?: string;
+}
+
+export default class SettingForm extends React.PureComponent<Props, State> {
+ mounted: boolean;
+
+ constructor(props: Props) {
+ super(props);
+ this.state = { submitting: false, value: props.setting.value };
+ }
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ handleSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
+ event.preventDefault();
+
+ const { value } = this.state;
+ if (!value) {
+ return;
+ }
+
+ this.setState({ submitting: true });
+ setSimpleSettingValue({
+ branch: this.props.branch,
+ component: this.props.project,
+ key: this.props.setting.key,
+ value
+ }).then(this.props.onChange, () => {
+ if (this.mounted) {
+ this.setState({ submitting: false });
+ }
+ });
+ };
+
+ handleValueChange = (event: React.SyntheticEvent<HTMLInputElement>) => {
+ this.setState({ value: event.currentTarget.value });
+ };
+
+ handleResetClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.setState({ submitting: true });
+ resetSettingValue(this.props.setting.key, this.props.project, this.props.branch).then(
+ this.props.onChange,
+ () => {
+ if (this.mounted) {
+ this.setState({ submitting: false });
+ }
+ }
+ );
+ };
+
+ handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ this.props.onClose();
+ };
+
+ render() {
+ const { setting } = this.props;
+ const submitDisabled = this.state.submitting || this.state.value === setting.value;
+
+ return (
+ <form onSubmit={this.handleSubmit}>
+ <div className="modal-body">
+ <div
+ className="big-spacer-bottom markdown"
+ dangerouslySetInnerHTML={{ __html: translate(`property.${setting.key}.description`) }}
+ />
+ <div className="big-spacer-bottom">
+ <input
+ autoFocus={true}
+ className="input-super-large"
+ onChange={this.handleValueChange}
+ required={true}
+ type="text"
+ value={this.state.value}
+ />
+ {setting.inherited && (
+ <div className="note spacer-top">{translate('settings._default')}</div>
+ )}
+ {!setting.inherited &&
+ setting.parentValue && (
+ <div className="note spacer-top">
+ {translateWithParameters('settings.default_x', setting.parentValue)}
+ </div>
+ )}
+ </div>
+ </div>
+ <footer className="modal-foot">
+ {!setting.inherited &&
+ setting.parentValue && (
+ <button
+ className="pull-left"
+ disabled={this.state.submitting}
+ onClick={this.handleResetClick}
+ type="reset">
+ {translate('reset_to_default')}
+ </button>
+ )}
+ {this.state.submitting && <i className="spinner spacer-right" />}
+ <button disabled={submitDisabled} type="submit">
+ {translate('save')}
+ </button>
+ <a href="#" onClick={this.handleCancelClick}>
+ {translate('cancel')}
+ </a>
+ </footer>
+ </form>
+ );
+ }
+}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+jest.mock('../../../../api/settings', () => ({
+ getValues: jest.fn(() => Promise.resolve([]))
+}));
+
import * as React from 'react';
-import { shallow } from 'enzyme';
+import { mount, shallow } from 'enzyme';
import App from '../App';
import { Branch, BranchType } from '../../../../app/types';
+const getValues = require('../../../../api/settings').getValues as jest.Mock<any>;
+
+beforeEach(() => {
+ getValues.mockClear();
+});
+
it('renders sorted list of branches', () => {
const branches: Branch[] = [
{ isMain: true, name: 'master' },
{ isMain: false, name: 'branch-1.0', type: BranchType.LONG },
{ isMain: false, name: 'branch-1.0', mergeBranch: 'master', type: BranchType.SHORT }
];
- expect(
- shallow(<App branches={branches} component={{ key: 'foo' }} onBranchesChange={jest.fn()} />)
- ).toMatchSnapshot();
+ const wrapper = shallow(
+ <App branches={branches} component={{ key: 'foo' }} onBranchesChange={jest.fn()} />
+ );
+ wrapper.setState({ branchLifeTime: '100', loading: false });
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('fetches branch life time setting on mount', () => {
+ mount(<App branches={[]} component={{ key: 'foo' }} onBranchesChange={jest.fn()} />);
+ expect(getValues).toBeCalledWith('sonar.dbcleaner.daysBeforeDeletingInactiveShortLivingBranches');
});
const mainBranch: MainBranch = { isMain: true, name: 'master' };
const shortBranch: ShortLivingBranch = {
+ analysisDate: '2017-09-27T00:05:19+0000',
isMain: false,
name: 'feature',
mergeBranch: 'foo',
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+jest.mock('../../../../api/settings', () => ({
+ getValues: jest.fn(() => Promise.resolve([]))
+}));
+
+import * as React from 'react';
+import { mount, shallow } from 'enzyme';
+import LongBranchesPattern from '../LongBranchesPattern';
+import { click } from '../../../../helpers/testUtils';
+
+const getValues = require('../../../../api/settings').getValues as jest.Mock<any>;
+
+beforeEach(() => {
+ getValues.mockClear();
+});
+
+it('renders', () => {
+ const wrapper = shallow(<LongBranchesPattern project="project" />);
+ wrapper.setState({ loading: false, setting: { value: 'release-.*' } });
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('opens form', () => {
+ const wrapper = shallow(<LongBranchesPattern project="project" />);
+ (wrapper.instance() as LongBranchesPattern) .mounted = true;
+ wrapper.setState({ loading: false, setting: { value: 'release-.*' } });
+
+ click(wrapper.find('a'));
+ expect(wrapper.find('LongBranchesPatternForm').exists()).toBeTruthy();
+
+ wrapper.find('LongBranchesPatternForm').prop<Function>('onClose')();
+ expect(wrapper.find('LongBranchesPatternForm').exists()).toBeFalsy();
+});
+
+it('fetches setting value on mount', () => {
+ mount(<LongBranchesPattern project="project" />);
+ expect(getValues).lastCalledWith('sonar.branch.longLivedBranches.regex', 'project');
+});
+
+it('fetches new setting value after change', () => {
+ const wrapper = mount(<LongBranchesPattern project="project" />);
+ expect(getValues.mock.calls).toHaveLength(1);
+
+ (wrapper.instance() as LongBranchesPattern).handleChange();
+ expect(getValues.mock.calls).toHaveLength(2);
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import LongBranchesPatternForm from '../LongBranchesPatternForm';
+
+it('renders', () => {
+ expect(
+ shallow(
+ <LongBranchesPatternForm
+ onChange={jest.fn()}
+ onClose={jest.fn()}
+ project="project"
+ setting={{ key: 'foo', value: 'bar' }}
+ />
+ )
+ ).toMatchSnapshot();
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+jest.mock('../../../../api/settings', () => ({
+ setSimpleSettingValue: jest.fn(() => Promise.resolve()),
+ resetSettingValue: jest.fn(() => Promise.resolve())
+}));
+
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import SettingForm from '../SettingForm';
+import { change, submit, click } from '../../../../helpers/testUtils';
+
+const setSimpleSettingValue = require('../../../../api/settings')
+ .setSimpleSettingValue as jest.Mock<any>;
+
+const resetSettingValue = require('../../../../api/settings').resetSettingValue as jest.Mock<any>;
+
+beforeEach(() => {
+ setSimpleSettingValue.mockClear();
+ resetSettingValue.mockClear();
+});
+
+it('changes value', async () => {
+ const onChange = jest.fn();
+ const wrapper = shallow(
+ <SettingForm
+ onChange={onChange}
+ onClose={jest.fn()}
+ project="project"
+ setting={{ inherited: true, key: 'foo', value: 'release-.*' }}
+ />
+ );
+ expect(wrapper).toMatchSnapshot();
+
+ change(wrapper.find('input'), 'branch-.*');
+ submit(wrapper.find('form'));
+ expect(setSimpleSettingValue).toBeCalledWith({
+ branch: undefined,
+ component: 'project',
+ key: 'foo',
+ value: 'branch-.*'
+ });
+
+ await new Promise(setImmediate);
+ expect(onChange).toBeCalled();
+});
+
+it('resets value', async () => {
+ const onChange = jest.fn();
+ const wrapper = shallow(
+ <SettingForm
+ onChange={onChange}
+ onClose={jest.fn()}
+ project="project"
+ setting={{ inherited: false, key: 'foo', parentValue: 'branch-.*', value: 'release-.*' }}
+ />
+ );
+ expect(wrapper).toMatchSnapshot();
+
+ click(wrapper.find('button[type="reset"]'));
+ expect(resetSettingValue).toBeCalledWith('foo', 'project', undefined);
+
+ await new Promise(setImmediate);
+ expect(onChange).toBeCalled();
+});
>
project_branches.page
</h1>
+ <LongBranchesPattern
+ project="foo"
+ />
+ <p
+ className="page-description"
+ >
+ project_branches.page.description
+ </p>
+ <p
+ className="page-description"
+ >
+ <FormattedMessage
+ defaultMessage="project_branches.page.life_time"
+ id="project_branches.page.life_time"
+ values={
+ Object {
+ "days": "100",
+ "settings": <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/admin/settings"
+ >
+ settings.page
+ </Link>,
+ }
+ }
+ />
+ </p>
</header>
<table
className="data zebra zebra-hover"
branch
</th>
<th
- className="text-right"
+ className="thin nowrap text-right"
>
status
</th>
<th
- className="text-right"
+ className="thin nowrap text-right"
+ >
+ project_history.last_snapshot
+ </th>
+ <th
+ className="thin nowrap text-right"
>
actions
</th>
}
/>
</td>
+ <td
+ className="thin nowrap text-right"
+ />
<td
className="thin nowrap text-right"
>
- <Tooltip
- overlay="branches.rename"
- placement="bottom"
+ <div
+ className="dropdown big-spacer-left"
>
- <a
- className="js-rename link-no-underline"
- href="#"
- onClick={[Function]}
+ <button
+ className="dropdown-toggle little-spacer-right button-compact"
+ data-toggle="dropdown"
+ >
+ <SettingsIcon
+ style={
+ Object {
+ "marginTop": 4,
+ }
+ }
+ />
+
+ <i
+ className="icon-dropdown"
+ />
+ </button>
+ <ul
+ className="dropdown-menu dropdown-menu-right"
>
- <ChangeIcon />
- </a>
- </Tooltip>
+ <li>
+ <a
+ className="js-rename link-no-underline"
+ href="#"
+ onClick={[Function]}
+ >
+ branches.rename
+ </a>
+ </li>
+ </ul>
+ </div>
</td>
</tr>
`;
<BranchIcon
branch={
Object {
+ "analysisDate": "2017-09-27T00:05:19+0000",
"isMain": false,
"mergeBranch": "foo",
"name": "feature",
<BranchStatus
branch={
Object {
+ "analysisDate": "2017-09-27T00:05:19+0000",
"isMain": false,
"mergeBranch": "foo",
"name": "feature",
<td
className="thin nowrap text-right"
>
- <Tooltip
- overlay="branches.delete"
- placement="bottom"
+ <DateFromNow
+ date="2017-09-27T00:05:19+0000"
+ />
+ </td>
+ <td
+ className="thin nowrap text-right"
+ >
+ <div
+ className="dropdown big-spacer-left"
>
- <a
- className="js-delete link-no-underline"
- href="#"
- onClick={[Function]}
+ <button
+ className="dropdown-toggle little-spacer-right button-compact"
+ data-toggle="dropdown"
+ >
+ <SettingsIcon
+ style={
+ Object {
+ "marginTop": 4,
+ }
+ }
+ />
+
+ <i
+ className="icon-dropdown"
+ />
+ </button>
+ <ul
+ className="dropdown-menu dropdown-menu-right"
>
- <DeleteIcon />
- </a>
- </Tooltip>
+ <li>
+ <a
+ className="js-delete link-no-underline"
+ href="#"
+ onClick={[Function]}
+ >
+ branches.delete
+ </a>
+ </li>
+ </ul>
+ </div>
</td>
</tr>
`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<div
+ className="pull-right text-right"
+>
+ branches.long_living_branches_pattern
+ :
+ <strong>
+ release-.*
+ </strong>
+ <a
+ className="display-inline-block spacer-left link-no-underline"
+ href="#"
+ onClick={[Function]}
+ >
+ <ChangeIcon />
+ </a>
+</div>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<Modal
+ ariaHideApp={true}
+ bodyOpenClassName="ReactModal__Body--open"
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="branches.detection_of_long_living_branches"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <header
+ className="modal-head"
+ >
+ <h2>
+ branches.detection_of_long_living_branches
+ </h2>
+ </header>
+ <SettingForm
+ onChange={[Function]}
+ onClose={[Function]}
+ project="project"
+ setting={
+ Object {
+ "key": "foo",
+ "value": "bar",
+ }
+ }
+ />
+</Modal>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`changes value 1`] = `
+<form
+ onSubmit={[Function]}
+>
+ <div
+ className="modal-body"
+ >
+ <div
+ className="big-spacer-bottom markdown"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "property.foo.description",
+ }
+ }
+ />
+ <div
+ className="big-spacer-bottom"
+ >
+ <input
+ autoFocus={true}
+ className="input-super-large"
+ onChange={[Function]}
+ required={true}
+ type="text"
+ value="release-.*"
+ />
+ <div
+ className="note spacer-top"
+ >
+ settings._default
+ </div>
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <button
+ disabled={true}
+ type="submit"
+ >
+ save
+ </button>
+ <a
+ href="#"
+ onClick={[Function]}
+ >
+ cancel
+ </a>
+ </footer>
+</form>
+`;
+
+exports[`resets value 1`] = `
+<form
+ onSubmit={[Function]}
+>
+ <div
+ className="modal-body"
+ >
+ <div
+ className="big-spacer-bottom markdown"
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "property.foo.description",
+ }
+ }
+ />
+ <div
+ className="big-spacer-bottom"
+ >
+ <input
+ autoFocus={true}
+ className="input-super-large"
+ onChange={[Function]}
+ required={true}
+ type="text"
+ value="release-.*"
+ />
+ <div
+ className="note spacer-top"
+ >
+ settings.default_x.branch-.*
+ </div>
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <button
+ className="pull-left"
+ disabled={false}
+ onClick={[Function]}
+ type="reset"
+ >
+ reset_to_default
+ </button>
+ <button
+ disabled={true}
+ type="submit"
+ >
+ save
+ </button>
+ <a
+ href="#"
+ onClick={[Function]}
+ >
+ cancel
+ </a>
+ </footer>
+</form>
+`;
const routes = [
{
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
- import('./components/App').then(i => callback(null, { component: (i as any).default }));
+ import('./components/AppContainer').then(i =>
+ callback(null, { component: (i as any).default })
+ );
}
}
];
// @flow
import React from 'react';
import Helmet from 'react-helmet';
-import { Link } from 'react-router';
-import { FormattedMessage } from 'react-intl';
import PageHeader from './PageHeader';
import CategoryDefinitionsList from './CategoryDefinitionsList';
import AllCategoriesList from './AllCategoriesList';
import WildcardsHelp from './WildcardsHelp';
-import { getBranchName } from '../../../helpers/branches';
import { translate } from '../../../helpers/l10n';
import '../styles.css';
/*::
type Props = {
- branch?: {},
component?: { key: string },
defaultCategory: ?string,
- fetchSettings(componentKey: ?string, branch?: string): Promise<*>,
+ fetchSettings(componentKey: ?string): Promise<*>,
location: { query: {} }
};
*/
html.classList.add('dashboard-page');
}
const componentKey = this.props.component ? this.props.component.key : null;
- const branch = this.props.branch && getBranchName(this.props.branch);
- this.props.fetchSettings(componentKey, branch).then(() => this.setState({ loaded: true }));
+ this.props.fetchSettings(componentKey).then(() => this.setState({ loaded: true }));
}
componentDidUpdate(prevProps /*: Props*/) {
if (prevProps.component !== this.props.component) {
const componentKey = this.props.component ? this.props.component.key : null;
- const branch = this.props.branch && getBranchName(this.props.branch);
- this.props.fetchSettings(componentKey, branch);
+ this.props.fetchSettings(componentKey);
}
}
const { query } = this.props.location;
const selectedCategory = query.category || this.props.defaultCategory;
- const branchName = this.props.branch && getBranchName(this.props.branch);
-
return (
<div id="settings-page" className="page page-limited">
<Helmet title={translate('settings.page')} />
- {branchName ? (
- <div className="alert alert-info">
- <FormattedMessage
- defaultMessage={translate('branches.settings_hint')}
- id="branches.settings_hint"
- values={{
- link: (
- <Link
- to={{
- pathname: '/project/branches',
- query: { id: this.props.component && this.props.component.key }
- }}>
- {translate('branches.settings_hint_tab')}
- </Link>
- )
- }}
- />
- </div>
- ) : (
- <PageHeader branch={branchName} component={this.props.component} />
- )}
+ <PageHeader component={this.props.component} />
+
<div className="side-tabs-layout settings-layout">
- {branchName == null && (
- <div className="side-tabs-side">
- <AllCategoriesList
- branch={branchName}
- component={this.props.component}
- selectedCategory={selectedCategory}
- defaultCategory={this.props.defaultCategory}
- />
- </div>
- )}
- <div className="side-tabs-main">
- <CategoryDefinitionsList
- branch={branchName}
+ <div className="side-tabs-side">
+ <AllCategoriesList
component={this.props.component}
- category={selectedCategory}
+ selectedCategory={selectedCategory}
+ defaultCategory={this.props.defaultCategory}
/>
-
+ </div>
+ <div className="side-tabs-main">
+ <CategoryDefinitionsList component={this.props.component} category={selectedCategory} />
{selectedCategory === 'exclusions' && <WildcardsHelp />}
</div>
</div>
/*::
type Props = {
- branch?: string,
categories: Category[],
component?: { key: string },
defaultCategory: string,
/*:: rops: Props; */
renderLink(category /*: Category */) {
- const query /*: Object */ = { branch: this.props.branch };
+ const query /*: Object */ = {};
if (category.key !== this.props.defaultCategory) {
query.category = category.key.toLowerCase();
/*:: timeout: number; */
static propTypes = {
- branch: PropTypes.string,
component: PropTypes.object,
setting: PropTypes.object.isRequired,
changedValue: PropTypes.any,
const componentKey = this.props.component ? this.props.component.key : null;
const { definition } = this.props.setting;
return this.props
- .resetValue(definition.key, componentKey, this.props.branch)
+ .resetValue(definition.key, componentKey)
.then(() => {
this.safeSetState({ success: true });
this.timeout = setTimeout(() => this.safeSetState({ success: false }), 3000);
this.safeSetState({ success: false });
const componentKey = this.props.component ? this.props.component.key : null;
this.props
- .saveValue(this.props.setting.definition.key, componentKey, this.props.branch)
+ .saveValue(this.props.setting.definition.key, componentKey)
.then(() => {
this.safeSetState({ success: true });
this.timeout = setTimeout(() => this.safeSetState({ success: false }), 3000);
export default class DefinitionsList extends React.PureComponent {
static propTypes = {
- branch: PropTypes.string,
component: PropTypes.object,
settings: PropTypes.array.isRequired
};
<ul className="settings-definitions-list">
{this.props.settings.map(setting => (
<li key={setting.definition.key}>
- <Definition
- branch={this.props.branch}
- component={this.props.component}
- setting={setting}
- />
+ <Definition component={this.props.component} setting={setting} />
</li>
))}
</ul>
export default class PageHeader extends React.PureComponent {
static propTypes = {
- branch: PropTypes.string,
component: PropTypes.object
};
export default class SubCategoryDefinitionsList extends React.PureComponent {
static propTypes = {
- branch: PropTypes.string,
component: PropTypes.object,
settings: PropTypes.array.isRequired
};
/>
)}
<DefinitionsList
- branch={this.props.branch}
component={this.props.component}
settings={bySubCategory[subCategory.key]}
/>
import { translate } from '../../../helpers/l10n';
import { getSettingsAppDefinition, getSettingsAppChangedValue } from '../../../store/rootReducer';
-export const fetchSettings = (componentKey, branch) => dispatch => {
- return getDefinitions(componentKey, branch)
+export const fetchSettings = componentKey => dispatch => {
+ return getDefinitions(componentKey)
.then(definitions => {
- const withoutLicenses = definitions.filter(definition => definition.type !== 'LICENSE');
- dispatch(receiveDefinitions(withoutLicenses));
- const keys = withoutLicenses.map(definition => definition.key).join();
- return getValues(keys, componentKey, branch);
+ const filtered = definitions
+ .filter(definition => definition.type !== 'LICENSE')
+ // do not display this setting on project level
+ .filter(
+ definition =>
+ componentKey == null || definition.key !== 'sonar.branch.longLivedBranches.regex'
+ );
+ dispatch(receiveDefinitions(filtered));
+ const keys = filtered.map(definition => definition.key).join();
+ return getValues(keys, componentKey);
})
.then(settings => {
dispatch(receiveValues(settings, componentKey));
.catch(e => parseError(e).then(message => dispatch(addGlobalErrorMessage(message))));
};
-export const saveValue = (key, componentKey, branch) => (dispatch, getState) => {
+export const saveValue = (key, componentKey) => (dispatch, getState) => {
dispatch(startLoading(key));
const state = getState();
return Promise.reject();
}
- return setSettingValue(definition, value, componentKey, branch)
- .then(() => getValues(key, componentKey, branch))
+ return setSettingValue(definition, value, componentKey)
+ .then(() => getValues(key, componentKey))
.then(values => {
dispatch(receiveValues(values, componentKey));
dispatch(cancelChange(key));
});
};
-export const resetValue = (key, componentKey, branch) => dispatch => {
+export const resetValue = (key, componentKey) => dispatch => {
dispatch(startLoading(key));
- return resetSettingValue(key, componentKey, branch)
- .then(() => getValues(key, componentKey, branch))
+ return resetSettingValue(key, componentKey)
+ .then(() => getValues(key, componentKey))
.then(values => {
if (values.length > 0) {
dispatch(receiveValues(values, componentKey));
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+
+interface Props {
+ className?: string;
+ fill?: string;
+ size?: number;
+ style?: React.CSSProperties;
+}
+
+export default function SettingsIcon({
+ className,
+ fill = 'currentColor',
+ size = 14,
+ style
+}: Props) {
+ return (
+ <svg
+ className={className}
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 14 14"
+ width={size}
+ height={size}
+ style={style}>
+ <g transform="matrix(0.0364583,0,0,0.0364583,0,-1.16667)">
+ <path
+ d="M256,224C256,206.333 249.75,191.25 237.25,178.75C224.75,166.25 209.667,160 192,160C174.333,160 159.25,166.25 146.75,178.75C134.25,191.25 128,206.333 128,224C128,241.667 134.25,256.75 146.75,269.25C159.25,281.75 174.333,288 192,288C209.667,288 224.75,281.75 237.25,269.25C249.75,256.75 256,241.667 256,224ZM384,196.75L384,252.25C384,254.25 383.333,256.167 382,258C380.667,259.833 379,260.917 377,261.25L330.75,268.25C327.583,277.25 324.333,284.833 321,291C326.833,299.333 335.75,310.833 347.75,325.5C349.417,327.5 350.25,329.583 350.25,331.75C350.25,333.917 349.5,335.833 348,337.5C343.5,343.667 335.25,352.667 323.25,364.5C311.25,376.333 303.417,382.25 299.75,382.25C297.75,382.25 295.583,381.5 293.25,380L258.75,353C251.417,356.833 243.833,360 236,362.5C233.333,385.167 230.917,400.667 228.75,409C227.583,413.667 224.583,416 219.75,416L164.25,416C161.917,416 159.875,415.292 158.125,413.875C156.375,412.458 155.417,410.667 155.25,408.5L148.25,362.5C140.083,359.833 132.583,356.75 125.75,353.25L90.5,380C88.833,381.5 86.75,382.25 84.25,382.25C81.917,382.25 79.833,381.333 78,379.5C57,360.5 43.25,346.5 36.75,337.5C35.583,335.833 35,333.917 35,331.75C35,329.75 35.667,327.833 37,326C39.5,322.5 43.75,316.958 49.75,309.375C55.75,301.792 60.25,295.917 63.25,291.75C58.75,283.417 55.333,275.167 53,267L7.25,260.25C5.083,259.917 3.333,258.875 2,257.125C0.667,255.375 0,253.417 0,251.25L0,195.75C0,193.75 0.667,191.833 2,190C3.333,188.167 4.917,187.083 6.75,186.75L53.25,179.75C55.583,172.083 58.833,164.417 63,156.75C56.333,147.25 47.417,135.75 36.25,122.25C34.583,120.25 33.75,118.25 33.75,116.25C33.75,114.583 34.5,112.667 36,110.5C40.333,104.5 48.542,95.542 60.625,83.625C72.708,71.708 80.583,65.75 84.25,65.75C86.417,65.75 88.583,66.583 90.75,68.25L125.25,95C132.583,91.167 140.167,88 148,85.5C150.667,62.833 153.083,47.333 155.25,39C156.417,34.333 159.417,32 164.25,32L219.75,32C222.083,32 224.125,32.708 225.875,34.125C227.625,35.542 228.583,37.333 228.75,39.5L235.75,85.5C243.917,88.167 251.417,91.25 258.25,94.75L293.75,68C295.25,66.5 297.25,65.75 299.75,65.75C301.917,65.75 304,66.583 306,68.25C327.5,88.083 341.25,102.25 347.25,110.75C348.417,112.083 349,113.917 349,116.25C349,118.25 348.333,120.167 347,122C344.5,125.5 340.25,131.042 334.25,138.625C328.25,146.208 323.75,152.083 320.75,156.25C325.083,164.583 328.5,172.75 331,180.75L376.75,187.75C378.917,188.083 380.667,189.125 382,190.875C383.333,192.625 384,194.583 384,196.75Z"
+ style={{ fill }}
+ />
+ </g>
+ </svg>
+ );
+}
}
.modal-foot {
- text-align: right;
- padding: 8px 10px;
+ line-height: 24px;
+ padding: 10px;
border-top: 1px solid #ccc;
- line-height: 30px;
background-color: #efefef;
+ text-align: right;
button,
.button,
content: '\f03a';
font-size: @iconSmallFontSize;
}
-.icon-settings:before {
- content: '\f015';
-}
.icon-bulk-change:before {
content: '\f085';
font-size: @iconSmallFontSize;
remove=Remove
rename=Rename
reset_verb=Reset
+reset_to_default=Reset To Default
resolution=Resolution
restart=Restart
restore=Restore
provisioning.page=Provisioning
provisioning.page.description=Use this page to initialize projects if you would like to configure them before the first analysis. Once a project is provisioned, you have access to perform all project configurations on it.
project_branches.page=Branches
+project_branches.page.description=Use this page to manage project branches.
+project_branches.page.life_time=Short living branches are permanently deleted when there is no analysis for {days} days.
+project_branches.page.life_time.admin=Short living branches are permanently deleted when there is no analysis for {days} days. You can change this parameter in {settings}.
#------------------------------------------------------------------------------
#
property.error.notRegexp=Not a valid Java regular expression
property.error.notInOptions=Not a valid option
property.category.scm=SCM
+property.sonar.leak.period.description=Period used to compare measures and track new issues. Values are:<ul class='bullet'><li>Number of days before analysis, for example 5.</li><li>A custom date. Format is yyyy-MM-dd, for example 2010-12-25</li><li>'previous_version' to compare to the previous version in the project history</li><li>A version, for example '1.2' or 'BASELINE'</li></ul><p>When specifying a number of days or a date, the snapshot selected for comparison is the first one available inside the corresponding time range. </p><p>Changing this property only takes effect after subsequent project inspections.<p/>
+property.sonar.branch.longLivedBranches.regex.description=Regular expression used to detect whether a branch is a long living branch (as opposed to short living branch), based on its name. This applies only during first analysis, the type of a branch cannot be changed later.
#------------------------------------------------------------------------------
#
branches.orphan_branches.tooltip=When a target branch of a short-living branch was deleted, this short-living branch becomes orphan.
branches.main_branch=Main Branch
branches.branch_settings=Branch Settings
-branches.settings_hint=To administrate your branches, you have to go to your main branch's {link} tab.
-branches.settings_hint_tab=Administration > Branches
+branches.long_living_branches_pattern=Long living branches pattern
+branches.detection_of_long_living_branches=Detection of long living branches
+branches.detection_of_long_living_branches.description=Regular expression used to detect whether a branch is a long living branch (as opposed to short living branch), based on its name. This applies only during first analysis, the type of a branch cannot be changed later.
+branches.set_leak_period=Set leak period
#------------------------------------------------------------------------------