*/
import { getJSON, post, postJSON } from '../helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
-import { Metric } from '../app/types';
-
-export interface ConditionBase {
- error: string;
- metric: string;
- op?: string;
- period?: number;
- warning: string;
-}
-
-export interface Condition extends ConditionBase {
- id: number;
-}
-
-export interface QualityGate {
- actions?: {
- associateProjects: boolean;
- copy: boolean;
- delete: boolean;
- manageConditions: boolean;
- rename: boolean;
- setAsDefault: boolean;
- };
- conditions?: Condition[];
- id: number;
- isBuiltIn?: boolean;
- isDefault?: boolean;
- name: string;
-}
+import { Condition, Metric, QualityGate } from '../app/types';
export function fetchQualityGates(data: {
organization?: string;
data: {
gateId: number;
organization?: string;
- } & ConditionBase
+ } & Condition
): Promise<Condition> {
return postJSON('/api/qualitygates/create_condition', data);
}
export function searchGates(data: {
gateId: number;
organization?: string;
- page: number;
- pageSize: number;
- selected: string;
-}): Promise<void | Response> {
+ page?: number;
+ pageSize?: number;
+ query?: string;
+ selected?: string;
+}): Promise<{ more: boolean; results: Array<{ id: string; name: string; selected: boolean }> }> {
return getJSON('/api/qualitygates/search', data).catch(throwGlobalError);
}
pointer-events: none !important;
}
+.menu > li > a.text-muted {
+ color: var(--secondFontColor);
+}
+
.menu > li > a:hover,
.menu > li > a:focus {
text-decoration: none;
}
.text-muted {
- color: var(--secondFontColor) !important;
+ color: var(--secondFontColor);
}
.text-muted-2 {
showUpdateKey?: boolean;
}
+export interface Condition {
+ error: string;
+ id?: number;
+ metric: string;
+ op?: string;
+ period?: number;
+ warning: string;
+}
+
export interface CoveredFile {
key: string;
longName: string;
url?: string;
}
+export interface QualityGate {
+ actions?: {
+ associateProjects?: boolean;
+ copy?: boolean;
+ delete?: boolean;
+ manageConditions?: boolean;
+ rename?: boolean;
+ setAsDefault?: boolean;
+ };
+ conditions?: Condition[];
+ id: number;
+ isBuiltIn?: boolean;
+ isDefault?: boolean;
+ name: string;
+}
+
export interface Rule {
isTemplate?: boolean;
key: string;
fetchQualityGates,
getGateForProject,
associateGateWithProject,
- dissociateGateWithProject,
- QualityGate
+ dissociateGateWithProject
} from '../../api/quality-gates';
import Suggestions from '../../app/components/embed-docs-modal/Suggestions';
import addGlobalSuccessMessage from '../../app/utils/addGlobalSuccessMessage';
import handleRequiredAuthorization from '../../app/utils/handleRequiredAuthorization';
-import { Component } from '../../app/types';
+import { Component, QualityGate } from '../../app/types';
import { translate } from '../../helpers/l10n';
interface Props {
const { allGates, gate, loading } = this.state;
return (
- <div id="project-quality-gate" className="page page-limited">
+ <div className="page page-limited" id="project-quality-gate">
<Suggestions suggestions="project_quality_gate" />
<Helmet title={translate('project_quality_gate.page')} />
<Header />
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { QualityGate } from '../../api/quality-gates';
import Select from '../../components/controls/Select';
import { translate } from '../../helpers/l10n';
+import { QualityGate } from '../../app/types';
interface Props {
allGates: QualityGate[];
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { omitBy, map, sortBy } from 'lodash';
-import Select from '../../../components/controls/Select';
-import { translate, getLocalizedMetricName, getLocalizedMetricDomain } from '../../../helpers/l10n';
-
-export default function AddConditionForm({ metrics, onSelect }) {
- function handleChange(option) {
- const metric = option.value;
-
- // e.target.value = '';
- onSelect(metric);
- }
-
- const metricsToDisplay = omitBy(metrics, metric => metric.hidden);
- const options = sortBy(
- map(metricsToDisplay, metric => ({
- value: metric.key,
- label: getLocalizedMetricName(metric),
- domain: metric.domain
- })),
- 'domain'
- );
-
- // use "disabled" property to emulate optgroups
- const optionsWithDomains = [];
- options.forEach((option, index, options) => {
- const previous = index > 0 ? options[index - 1] : null;
- if (!previous || previous.domain !== option.domain) {
- optionsWithDomains.push({
- value: option.domain,
- label: getLocalizedMetricDomain(option.domain),
- disabled: true
- });
- }
- optionsWithDomains.push(option);
- });
-
- return (
- <div className="big-spacer-top panel bg-muted">
- <Select
- id="quality-gate-new-condition-metric"
- className="text-middle input-large"
- options={optionsWithDomains}
- placeholder={translate('quality_gates.add_condition')}
- onChange={handleChange}
- />
- </div>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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';
+import { sortBy } from 'lodash';
+import Select from '../../../components/controls/Select';
+import { translate, getLocalizedMetricName, getLocalizedMetricDomain } from '../../../helpers/l10n';
+import { Metric } from '../../../app/types';
+
+interface Props {
+ metrics: Metric[];
+ onAddCondition: (metric: string) => void;
+}
+
+interface Option {
+ disabled?: boolean;
+ domain?: string;
+ label: string;
+ value: string;
+}
+
+export default class AddConditionSelect extends React.PureComponent<Props> {
+ handleChange = (option: Option) => {
+ this.props.onAddCondition(option.value);
+ };
+
+ render() {
+ const { metrics } = this.props;
+
+ const options: Option[] = sortBy(
+ metrics.map(metric => ({
+ value: metric.key,
+ label: getLocalizedMetricName(metric),
+ domain: metric.domain
+ })),
+ 'domain'
+ );
+
+ // use "disabled" property to emulate optgroups
+ const optionsWithDomains: Option[] = [];
+ options.forEach((option, index, options) => {
+ const previous = index > 0 ? options[index - 1] : null;
+ if (option.domain && (!previous || previous.domain !== option.domain)) {
+ optionsWithDomains.push({
+ value: option.domain,
+ label: getLocalizedMetricDomain(option.domain),
+ disabled: true
+ });
+ }
+ optionsWithDomains.push(option);
+ });
+
+ return (
+ <div className="big-spacer-top panel bg-muted">
+ <Select
+ className="text-middle input-large"
+ onChange={this.handleChange}
+ options={optionsWithDomains}
+ placeholder={translate('quality_gates.add_condition')}
+ />
+ </div>
+ );
+ }
+}
import * as React from 'react';
import DeleteConditionForm from './DeleteConditionForm';
import ThresholdInput from './ThresholdInput';
-import {
- Condition as ICondition,
- ConditionBase,
- createCondition,
- QualityGate,
- updateCondition
-} from '../../../api/quality-gates';
-import { Metric } from '../../../app/types';
+import { createCondition, updateCondition } from '../../../api/quality-gates';
+import { Condition as ICondition, Metric, QualityGate } from '../../../app/types';
import Checkbox from '../../../components/controls/Checkbox';
import Select from '../../../components/controls/Select';
import { Button, ResetButtonLink } from '../../../components/ui/buttons';
import { translate, getLocalizedMetricName } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
+import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
interface Props {
condition: ICondition;
- edit: boolean;
+ canEdit: boolean;
metric: Metric;
- organization: string;
- onDeleteCondition: (condition: ICondition) => void;
+ organization?: string;
+ onAddCondition: (metric: string) => void;
onError: (error: any) => void;
+ onRemoveCondition: (Condition: ICondition) => void;
onResetError: () => void;
- onSaveCondition: (condition: ICondition, newCondition: ICondition) => void;
+ onSaveCondition: (newCondition: ICondition, oldCondition: ICondition) => void;
qualityGate: QualityGate;
}
interface State {
changed: boolean;
- period?: number;
+ error: string;
op?: string;
- openDeleteCondition: boolean;
+ period?: number;
warning: string;
- error: string;
}
export default class Condition extends React.PureComponent<Props, State> {
changed: false,
period: props.condition.period,
op: props.condition.op,
- openDeleteCondition: false,
warning: props.condition.warning || '',
error: props.condition.error || ''
};
}
- handleOperatorChange = ({ value }: any) => this.setState({ changed: true, op: value });
-
- handlePeriodChange = (checked: boolean) => {
- const period = checked ? 1 : undefined;
- this.setState({ changed: true, period });
- };
-
- handleWarningChange = (warning: string) => this.setState({ changed: true, warning });
-
- handleErrorChange = (error: string) => this.setState({ changed: true, error });
-
- handleSaveClick = () => {
- const { qualityGate, condition, metric, organization } = this.props;
- const { period } = this.state;
- const data: ConditionBase = {
- metric: condition.metric,
+ getUpdatedCondition = () => {
+ const { metric } = this.props;
+ const data: ICondition = {
+ metric: metric.key,
op: metric.type === 'RATING' ? 'GT' : this.state.op,
warning: this.state.warning,
error: this.state.error
};
+ const { period } = this.state;
if (period && metric.type !== 'RATING') {
data.period = period;
}
- if (metric.key.indexOf('new_') === 0) {
+ if (isDiffMetric(metric.key)) {
data.period = 1;
}
+ return data;
+ };
+
+ handleOperatorChange = ({ value }: any) => this.setState({ changed: true, op: value });
+
+ handlePeriodChange = (checked: boolean) => {
+ const period = checked ? 1 : undefined;
+ this.setState({ changed: true, period });
+ };
+
+ handleWarningChange = (warning: string) => this.setState({ changed: true, warning });
+ handleErrorChange = (error: string) => this.setState({ changed: true, error });
+
+ handleSaveClick = () => {
+ const { qualityGate, organization } = this.props;
+ const data = this.getUpdatedCondition();
createCondition({ gateId: qualityGate.id, organization, ...data }).then(
this.handleConditionResponse,
this.props.onError
};
handleUpdateClick = () => {
- const { condition, metric, organization } = this.props;
- const { period } = this.state;
+ const { condition, organization } = this.props;
const data: ICondition = {
id: condition.id,
- metric: condition.metric,
- op: metric.type === 'RATING' ? 'GT' : this.state.op,
- warning: this.state.warning,
- error: this.state.error
+ ...this.getUpdatedCondition()
};
- if (period && metric.type !== 'RATING') {
- data.period = period;
- }
-
- if (metric.key.indexOf('new_') === 0) {
- data.period = 1;
- }
-
updateCondition({ organization, ...data }).then(
this.handleConditionResponse,
this.props.onError
};
handleConditionResponse = (newCondition: ICondition) => {
- this.setState({ changed: false });
- this.props.onSaveCondition(this.props.condition, newCondition);
+ this.props.onSaveCondition(newCondition, this.props.condition);
this.props.onResetError();
+ this.setState({ changed: false });
};
handleCancelClick = () => {
- this.props.onDeleteCondition(this.props.condition);
- };
-
- openDeleteConditionForm = () => {
- this.setState({ openDeleteCondition: true });
- };
-
- closeDeleteConditionForm = () => {
- this.setState({ openDeleteCondition: false });
+ this.props.onRemoveCondition(this.props.condition);
};
renderPeriodValue() {
const { condition, metric } = this.props;
const isLeakSelected = !!this.state.period;
- const isDiffMetric = condition.metric.indexOf('new_') === 0;
const isRating = metric.type === 'RATING';
- if (isDiffMetric) {
+ if (isDiffMetric(condition.metric)) {
return (
<span className="note">{translate('quality_gates.condition.leak.unconditional')}</span>
);
}
renderPeriod() {
- const { condition, metric, edit } = this.props;
-
- const isDiffMetric = condition.metric.indexOf('new_') === 0;
+ const { condition, metric, canEdit } = this.props;
const isRating = metric.type === 'RATING';
const isLeakSelected = !!this.state.period;
- if (isRating || isDiffMetric || !edit) {
+ if (isRating || isDiffMetric(condition.metric) || !canEdit) {
return this.renderPeriodValue();
}
}
renderOperator() {
- const { condition, edit, metric } = this.props;
+ const { condition, canEdit, metric } = this.props;
- if (!edit && condition.op) {
+ if (!canEdit && condition.op) {
return metric.type === 'RATING'
? translate('quality_gates.operator', condition.op, 'rating')
: translate('quality_gates.operator', condition.op);
}
render() {
- const { condition, edit, metric, organization } = this.props;
+ const { condition, canEdit, metric, organization } = this.props;
return (
<tr>
<td className="text-middle">
<td className="thin text-middle nowrap">{this.renderOperator()}</td>
<td className="thin text-middle nowrap">
- {edit ? (
+ {canEdit ? (
<ThresholdInput
metric={metric}
name="warning"
</td>
<td className="thin text-middle nowrap">
- {edit ? (
+ {canEdit ? (
<ThresholdInput
metric={metric}
name="error"
)}
</td>
- {edit && (
+ {canEdit && (
<td className="thin text-middle nowrap">
{condition.id ? (
<div>
onClick={this.handleUpdateClick}>
{translate('update_verb')}
</Button>
- <Button
- className="button-red delete-condition little-spacer-left"
- onClick={this.openDeleteConditionForm}>
- {translate('delete')}
- </Button>
- {this.state.openDeleteCondition && (
- <DeleteConditionForm
- condition={condition}
- metric={metric}
- onClose={this.closeDeleteConditionForm}
- onDelete={this.props.onDeleteCondition}
- organization={organization}
- />
- )}
+ <DeleteConditionForm
+ condition={condition}
+ metric={metric}
+ onDelete={this.props.onRemoveCondition}
+ organization={organization}
+ />
</div>
) : (
<div>
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { sortBy, uniqBy } from 'lodash';
-import AddConditionForm from './AddConditionForm';
-import Condition from './Condition';
-import DocTooltip from '../../../components/docs/DocTooltip';
-import { translate, getLocalizedMetricName } from '../../../helpers/l10n';
-
-function getKey(condition, index) {
- return condition.id ? condition.id : `new-${index}`;
-}
-
-export default class Conditions extends React.PureComponent {
- state = {
- error: null
- };
-
- componentWillUpdate(nextProps) {
- if (nextProps.qualityGate !== this.props.qualityGate) {
- this.setState({ error: null });
- }
- }
-
- handleError(error) {
- try {
- error.response.json().then(r => {
- const message = r.errors.map(e => e.msg).join('. ');
- this.setState({ error: message });
- });
- } catch (ex) {
- this.setState({ error: translate('default_error_message') });
- }
- }
-
- handleResetError() {
- this.setState({ error: null });
- }
-
- render() {
- const {
- qualityGate,
- conditions,
- metrics,
- edit,
- onAddCondition,
- onSaveCondition,
- onDeleteCondition,
- organization
- } = this.props;
-
- const existingConditions = conditions.filter(condition => metrics[condition.metric]);
-
- const sortedConditions = sortBy(
- existingConditions,
- condition => metrics[condition.metric] && metrics[condition.metric].name
- );
-
- const duplicates = [];
- const savedConditions = existingConditions.filter(condition => condition.id != null);
- savedConditions.forEach(condition => {
- const sameCount = savedConditions.filter(
- sample => sample.metric === condition.metric && sample.period === condition.period
- ).length;
- if (sameCount > 1) {
- duplicates.push(condition);
- }
- });
-
- const uniqDuplicates = uniqBy(duplicates, d => d.metric).map(condition => ({
- ...condition,
- metric: metrics[condition.metric]
- }));
- return (
- <div className="quality-gate-section" id="quality-gate-conditions">
- <header className="display-flex-center spacer-bottom">
- <h3>{translate('quality_gates.conditions')}</h3>
- <DocTooltip className="spacer-left" doc="quality-gates/quality-gate-conditions" />
- </header>
-
- <div className="big-spacer-bottom">{translate('quality_gates.introduction')}</div>
-
- {this.state.error && <div className="alert alert-danger">{this.state.error}</div>}
-
- {uniqDuplicates.length > 0 && (
- <div className="alert alert-warning">
- <p>{translate('quality_gates.duplicated_conditions')}</p>
- <ul className="list-styled spacer-top">
- {uniqDuplicates.map(d => (
- <li key={d.metric.key}>{getLocalizedMetricName(d.metric)}</li>
- ))}
- </ul>
- </div>
- )}
-
- {sortedConditions.length ? (
- <table className="data zebra zebra-hover" id="quality-gate-conditions">
- <thead>
- <tr>
- <th className="nowrap">{translate('quality_gates.conditions.metric')}</th>
- <th className="thin nowrap">{translate('quality_gates.conditions.leak')}</th>
- <th className="thin nowrap">{translate('quality_gates.conditions.operator')}</th>
- <th className="thin nowrap">{translate('quality_gates.conditions.warning')}</th>
- <th className="thin nowrap">{translate('quality_gates.conditions.error')}</th>
- {edit && <th />}
- </tr>
- </thead>
- <tbody>
- {sortedConditions.map((condition, index) => (
- <Condition
- key={getKey(condition, index)}
- qualityGate={qualityGate}
- condition={condition}
- metric={metrics[condition.metric]}
- edit={edit}
- onSaveCondition={onSaveCondition}
- onDeleteCondition={onDeleteCondition}
- onError={this.handleError.bind(this)}
- onResetError={this.handleResetError.bind(this)}
- organization={organization}
- />
- ))}
- </tbody>
- </table>
- ) : (
- <div className="big-spacer-top">{translate('quality_gates.no_conditions')}</div>
- )}
-
- {edit && <AddConditionForm metrics={metrics} onSelect={onAddCondition} />}
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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';
+import { differenceWith, map, sortBy, uniqBy } from 'lodash';
+import AddConditionSelect from './AddConditionSelect';
+import Condition from './Condition';
+import DocTooltip from '../../../components/docs/DocTooltip';
+import { translate, getLocalizedMetricName } from '../../../helpers/l10n';
+import { Condition as ICondition, Metric, QualityGate } from '../../../app/types';
+import { parseError } from '../../../helpers/request';
+
+interface Props {
+ canEdit: boolean;
+ conditions: ICondition[];
+ metrics: { [key: string]: Metric };
+ onAddCondition: (metric: string) => void;
+ onSaveCondition: (newCondition: ICondition, oldCondition: ICondition) => void;
+ onRemoveCondition: (Condition: ICondition) => void;
+ organization?: string;
+ qualityGate: QualityGate;
+}
+
+interface State {
+ error?: string;
+}
+
+export default class Conditions extends React.PureComponent<Props, State> {
+ state: State = {};
+
+ componentWillUpdate(nextProps: Props) {
+ if (nextProps.qualityGate !== this.props.qualityGate) {
+ this.setState({ error: undefined });
+ }
+ }
+
+ getConditionKey = (condition: ICondition, index: number) => {
+ return condition.id ? condition.id : `new-${index}`;
+ };
+
+ handleError = (error: any) => {
+ parseError(error).then(
+ message => {
+ this.setState({ error: message });
+ },
+ () => {}
+ );
+ };
+
+ handleResetError = () => {
+ this.setState({ error: undefined });
+ };
+
+ render() {
+ const { qualityGate, conditions, metrics, canEdit, organization } = this.props;
+
+ const existingConditions = conditions.filter(condition => metrics[condition.metric]);
+
+ const sortedConditions = sortBy(
+ existingConditions,
+ condition => metrics[condition.metric] && metrics[condition.metric].name
+ );
+
+ const duplicates: ICondition[] = [];
+ const savedConditions = existingConditions.filter(condition => condition.id != null);
+ savedConditions.forEach(condition => {
+ const sameCount = savedConditions.filter(
+ sample => sample.metric === condition.metric && sample.period === condition.period
+ ).length;
+ if (sameCount > 1) {
+ duplicates.push(condition);
+ }
+ });
+
+ const uniqDuplicates = uniqBy(duplicates, d => d.metric).map(condition => ({
+ ...condition,
+ metric: metrics[condition.metric]
+ }));
+
+ const availableMetrics = differenceWith(
+ map(metrics, metric => metric).filter(
+ metric => !metric.hidden && !['DATA', 'DISTRIB'].includes(metric.type)
+ ),
+ conditions,
+ (metric, condition) => metric.key === condition.metric
+ );
+
+ return (
+ <div className="quality-gate-section" id="quality-gate-conditions">
+ <header className="display-flex-center spacer-bottom">
+ <h3>{translate('quality_gates.conditions')}</h3>
+ <DocTooltip className="spacer-left" doc="quality-gates/quality-gate-conditions" />
+ </header>
+
+ <div className="big-spacer-bottom">{translate('quality_gates.introduction')}</div>
+
+ {this.state.error && <div className="alert alert-danger">{this.state.error}</div>}
+
+ {uniqDuplicates.length > 0 && (
+ <div className="alert alert-warning">
+ <p>{translate('quality_gates.duplicated_conditions')}</p>
+ <ul className="list-styled spacer-top">
+ {uniqDuplicates.map(d => (
+ <li key={d.metric.key}>{getLocalizedMetricName(d.metric)}</li>
+ ))}
+ </ul>
+ </div>
+ )}
+
+ {sortedConditions.length ? (
+ <table className="data zebra zebra-hover" id="quality-gate-conditions">
+ <thead>
+ <tr>
+ <th className="nowrap">{translate('quality_gates.conditions.metric')}</th>
+ <th className="thin nowrap">{translate('quality_gates.conditions.leak')}</th>
+ <th className="thin nowrap">{translate('quality_gates.conditions.operator')}</th>
+ <th className="thin nowrap">{translate('quality_gates.conditions.warning')}</th>
+ <th className="thin nowrap">{translate('quality_gates.conditions.error')}</th>
+ {canEdit && <th />}
+ </tr>
+ </thead>
+ <tbody>
+ {sortedConditions.map((condition, index) => (
+ <Condition
+ canEdit={canEdit}
+ condition={condition}
+ key={this.getConditionKey(condition, index)}
+ metric={metrics[condition.metric]}
+ onAddCondition={this.props.onAddCondition}
+ onError={this.handleError}
+ onRemoveCondition={this.props.onRemoveCondition}
+ onResetError={this.handleResetError}
+ onSaveCondition={this.props.onSaveCondition}
+ organization={organization}
+ qualityGate={qualityGate}
+ />
+ ))}
+ </tbody>
+ </table>
+ ) : (
+ <div className="big-spacer-top">{translate('quality_gates.no_conditions')}</div>
+ )}
+
+ {canEdit && (
+ <AddConditionSelect
+ metrics={availableMetrics}
+ onAddCondition={this.props.onAddCondition}
+ />
+ )}
+ </div>
+ );
+ }
+}
*/
import * as React from 'react';
import * as PropTypes from 'prop-types';
-import { copyQualityGate, QualityGate } from '../../../api/quality-gates';
-import Modal from '../../../components/controls/Modal';
-import { ResetButtonLink, SubmitButton } from '../../../components/ui/buttons';
+import { copyQualityGate } from '../../../api/quality-gates';
+import ConfirmButton from '../../../components/controls/ConfirmButton';
+import { Button } from '../../../components/ui/buttons';
import { translate } from '../../../helpers/l10n';
import { getQualityGateUrl } from '../../../helpers/urls';
+import { QualityGate } from '../../../app/types';
interface Props {
- qualityGate: QualityGate;
- onCopy: (newQualityGate: QualityGate) => void;
- onClose: () => void;
+ onCopy: () => Promise<void>;
organization?: string;
+ qualityGate: QualityGate;
}
interface State {
- loading: boolean;
name: string;
}
export default class CopyQualityGateForm extends React.PureComponent<Props, State> {
- mounted = false;
-
static contextTypes = {
router: PropTypes.object
};
constructor(props: Props) {
super(props);
- this.state = { loading: false, name: props.qualityGate.name };
- }
-
- componentDidMount() {
- this.mounted = true;
- }
-
- componentWillUnmount() {
- this.mounted = false;
+ this.state = { name: props.qualityGate.name };
}
- handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => {
+ handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ name: event.currentTarget.value });
};
- handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
- event.preventDefault();
+ onCopy = () => {
const { qualityGate, organization } = this.props;
const { name } = this.state;
- if (name) {
- this.setState({ loading: true });
- copyQualityGate({ id: qualityGate.id, name, organization }).then(
- qualityGate => {
- this.props.onCopy(qualityGate);
- this.props.onClose();
- this.context.router.push(
- getQualityGateUrl(String(qualityGate.id), this.props.organization)
- );
- },
- () => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
- }
- );
+
+ if (!name) {
+ return undefined;
}
+
+ return copyQualityGate({ id: qualityGate.id, name, organization }).then(qualityGate => {
+ this.props.onCopy();
+ this.context.router.push(getQualityGateUrl(String(qualityGate.id), this.props.organization));
+ });
};
render() {
const { qualityGate } = this.props;
- const { loading, name } = this.state;
- const header = translate('quality_gates.copy');
- const submitDisabled = loading || !name || (qualityGate && qualityGate.name === name);
+ const { name } = this.state;
+ const confirmDisable = !name || (qualityGate && qualityGate.name === name);
return (
- <Modal contentLabel={header} onRequestClose={this.props.onClose}>
- <form id="quality-gate-form" onSubmit={this.handleFormSubmit}>
- <div className="modal-head">
- <h2>{header}</h2>
+ <ConfirmButton
+ confirmButtonText={translate('copy')}
+ confirmDisable={confirmDisable}
+ modalBody={
+ <div className="modal-field">
+ <label htmlFor="quality-gate-form-name">
+ {translate('name')}
+ <em className="mandatory">*</em>
+ </label>
+ <input
+ autoFocus={true}
+ id="quality-gate-form-name"
+ maxLength={100}
+ onChange={this.handleNameChange}
+ required={true}
+ size={50}
+ type="text"
+ value={name}
+ />
</div>
- <div className="modal-body">
- <div className="modal-field">
- <label htmlFor="quality-gate-form-name">
- {translate('name')}
- <em className="mandatory">*</em>
- </label>
- <input
- autoFocus={true}
- id="quality-gate-form-name"
- maxLength={100}
- onChange={this.handleNameChange}
- required={true}
- size={50}
- type="text"
- value={name}
- />
- </div>
- </div>
- <div className="modal-foot">
- {loading && <i className="spinner spacer-right" />}
- <SubmitButton className="js-confirm" disabled={submitDisabled}>
- {translate('copy')}
- </SubmitButton>
- <ResetButtonLink className="js-modal-close" onClick={this.props.onClose}>
- {translate('cancel')}
- </ResetButtonLink>
- </div>
- </form>
- </Modal>
+ }
+ modalHeader={translate('quality_gates.copy')}
+ onConfirm={this.onCopy}>
+ {({ onClick }) => (
+ <Button className="little-spacer-left" id="quality-gate-copy" onClick={onClick}>
+ {translate('copy')}
+ </Button>
+ )}
+ </ConfirmButton>
);
}
}
*/
import * as React from 'react';
import * as PropTypes from 'prop-types';
-import { createQualityGate, QualityGate } from '../../../api/quality-gates';
-import Modal from '../../../components/controls/Modal';
-import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons';
+import { createQualityGate } from '../../../api/quality-gates';
+import ConfirmButton from '../../../components/controls/ConfirmButton';
+import { Button } from '../../../components/ui/buttons';
import { translate } from '../../../helpers/l10n';
import { getQualityGateUrl } from '../../../helpers/urls';
interface Props {
- onCreate: (qualityGate: QualityGate) => void;
- onClose: () => void;
+ onCreate: () => Promise<void>;
organization?: string;
}
interface State {
- loading: boolean;
name: string;
}
export default class CreateQualityGateForm extends React.PureComponent<Props, State> {
- mounted = false;
-
static contextTypes = {
router: PropTypes.object
};
- state = { loading: false, name: '' };
-
- componentDidMount() {
- this.mounted = true;
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
+ state = { name: '' };
handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => {
this.setState({ name: event.currentTarget.value });
};
- handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
- event.preventDefault();
+ handleCreate = () => {
const { organization } = this.props;
const { name } = this.state;
- if (name) {
- this.setState({ loading: true });
- createQualityGate({ name, organization }).then(
- qualityGate => {
- this.props.onCreate(qualityGate);
- this.context.router.push(getQualityGateUrl(String(qualityGate.id), organization));
- this.props.onClose();
- },
- () => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
- }
- );
+
+ if (!name) {
+ return undefined;
}
+
+ return createQualityGate({ name, organization })
+ .then(qualityGate => {
+ return this.props.onCreate().then(() => qualityGate);
+ })
+ .then(qualityGate => {
+ this.context.router.push(getQualityGateUrl(String(qualityGate.id), organization));
+ });
};
render() {
- const { loading, name } = this.state;
- const header = translate('quality_gates.create');
- const submitDisabled = loading || !name;
-
+ const { name } = this.state;
return (
- <Modal contentLabel={header} onRequestClose={this.props.onClose}>
- <form id="quality-gate-form" onSubmit={this.handleFormSubmit}>
- <div className="modal-head">
- <h2>{header}</h2>
+ <ConfirmButton
+ confirmButtonText={translate('save')}
+ confirmDisable={!name}
+ modalBody={
+ <div className="modal-field">
+ <label htmlFor="quality-gate-form-name">
+ {translate('name')}
+ <em className="mandatory">*</em>
+ </label>
+ <input
+ autoFocus={true}
+ id="quality-gate-form-name"
+ maxLength={100}
+ onChange={this.handleNameChange}
+ required={true}
+ size={50}
+ type="text"
+ value={name}
+ />
</div>
- <div className="modal-body">
- <div className="modal-field">
- <label htmlFor="quality-gate-form-name">
- {translate('name')}
- <em className="mandatory">*</em>
- </label>
- <input
- autoFocus={true}
- id="quality-gate-form-name"
- maxLength={100}
- onChange={this.handleNameChange}
- required={true}
- size={50}
- type="text"
- value={name}
- />
- </div>
- </div>
- <div className="modal-foot">
- {loading && <i className="spinner spacer-right" />}
- <SubmitButton className="js-confirm" disabled={submitDisabled}>
- {translate('save')}
- </SubmitButton>
- <ResetButtonLink className="js-modal-close" onClick={this.props.onClose}>
- {translate('cancel')}
- </ResetButtonLink>
- </div>
- </form>
- </Modal>
+ }
+ modalHeader={translate('quality_gates.create')}
+ onConfirm={this.handleCreate}>
+ {({ onClick }) => (
+ <Button id="quality-gate-add" onClick={onClick}>
+ {translate('create')}
+ </Button>
+ )}
+ </ConfirmButton>
);
}
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Condition, deleteCondition } from '../../../api/quality-gates';
-import { Metric } from '../../../app/types';
-import Modal from '../../../components/controls/Modal';
-import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons';
+import { deleteCondition } from '../../../api/quality-gates';
+import { Metric, Condition } from '../../../app/types';
+import ConfirmButton from '../../../components/controls/ConfirmButton';
+import { Button } from '../../../components/ui/buttons';
import { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n';
interface Props {
condition: Condition;
metric: Metric;
- onClose: () => void;
onDelete: (condition: Condition) => void;
organization?: string;
}
-interface State {
- loading: boolean;
-}
-
-export default class DeleteConditionForm extends React.PureComponent<Props, State> {
- mounted = false;
- state: State = { loading: false };
-
- componentDidMount() {
- this.mounted = true;
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
- event.preventDefault();
+export default class DeleteConditionForm extends React.PureComponent<Props> {
+ onDelete = () => {
const { organization, condition } = this.props;
- this.setState({ loading: true });
- deleteCondition({ id: condition.id, organization }).then(
- () => this.props.onDelete(condition),
- () => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
- }
- );
+ if (condition.id !== undefined) {
+ return deleteCondition({ id: condition.id, organization }).then(() =>
+ this.props.onDelete(condition)
+ );
+ }
+ return undefined;
};
render() {
- const { metric } = this.props;
- const header = translate('quality_gates.delete_condition');
-
return (
- <Modal contentLabel={header} onRequestClose={this.props.onClose}>
- <form id="delete-profile-form" onSubmit={this.handleFormSubmit}>
- <div className="modal-head">
- <h2>{header}</h2>
- </div>
- <div className="modal-body">
- <p>
- {translateWithParameters(
- 'quality_gates.delete_condition.confirm.message',
- getLocalizedMetricName(metric)
- )}
- </p>
- </div>
- <div className="modal-foot">
- {this.state.loading && <i className="spinner spacer-right" />}
- <SubmitButton className="js-delete button-red" disabled={this.state.loading}>
- {translate('delete')}
- </SubmitButton>
- <ResetButtonLink className="js-modal-close" onClick={this.props.onClose}>
- {translate('cancel')}
- </ResetButtonLink>
- </div>
- </form>
- </Modal>
+ <ConfirmButton
+ confirmButtonText={translate('delete')}
+ isDestructive={true}
+ modalBody={translateWithParameters(
+ 'quality_gates.delete_condition.confirm.message',
+ getLocalizedMetricName(this.props.metric)
+ )}
+ modalHeader={translate('quality_gates.delete_condition')}
+ onConfirm={this.onDelete}>
+ {({ onClick }) => (
+ <Button className="delete-condition little-spacer-left button-red" onClick={onClick}>
+ {translate('delete')}
+ </Button>
+ )}
+ </ConfirmButton>
);
}
}
*/
import * as React from 'react';
import * as PropTypes from 'prop-types';
-import { deleteQualityGate, QualityGate } from '../../../api/quality-gates';
-import Modal from '../../../components/controls/Modal';
-import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons';
+import { deleteQualityGate } from '../../../api/quality-gates';
+import ConfirmButton from '../../../components/controls/ConfirmButton';
+import { Button } from '../../../components/ui/buttons';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getQualityGatesUrl } from '../../../helpers/urls';
+import { QualityGate } from '../../../app/types';
interface Props {
- onClose: () => void;
- onDelete: (qualityGate: QualityGate) => void;
+ onDelete: () => Promise<void>;
organization?: string;
qualityGate: QualityGate;
}
-interface State {
- loading: boolean;
-}
-
-export default class DeleteQualityGateForm extends React.PureComponent<Props, State> {
- mounted = false;
-
+export default class DeleteQualityGateForm extends React.PureComponent<Props> {
static contextTypes = {
router: PropTypes.object
};
- state: State = { loading: false };
-
- componentDidMount() {
- this.mounted = true;
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
- event.preventDefault();
+ onDelete = () => {
const { organization, qualityGate } = this.props;
- this.setState({ loading: true });
- deleteQualityGate({ id: qualityGate.id, organization }).then(
- () => {
- this.props.onDelete(qualityGate);
- this.context.router.replace(getQualityGatesUrl(organization));
- },
- () => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
- }
- );
+ return deleteQualityGate({ id: qualityGate.id, organization })
+ .then(this.props.onDelete)
+ .then(() => {
+ this.context.router.push(getQualityGatesUrl(organization));
+ });
};
render() {
const { qualityGate } = this.props;
- const header = translate('quality_gates.delete');
return (
- <Modal contentLabel={header} onRequestClose={this.props.onClose}>
- <form id="delete-profile-form" onSubmit={this.handleFormSubmit}>
- <div className="modal-head">
- <h2>{header}</h2>
- </div>
- <div className="modal-body">
- <p>
- {translateWithParameters('quality_gates.delete.confirm.message', qualityGate.name)}
- </p>
- </div>
- <div className="modal-foot">
- {this.state.loading && <i className="spinner spacer-right" />}
- <SubmitButton className="js-delete button-red" disabled={this.state.loading}>
- {translate('delete')}
- </SubmitButton>
- <ResetButtonLink className="js-modal-close" onClick={this.props.onClose}>
- {translate('cancel')}
- </ResetButtonLink>
- </div>
- </form>
- </Modal>
+ <ConfirmButton
+ confirmButtonText={translate('delete')}
+ isDestructive={true}
+ modalBody={translateWithParameters(
+ 'quality_gates.delete.confirm.message',
+ qualityGate.name
+ )}
+ modalHeader={translate('quality_gates.delete')}
+ onConfirm={this.onDelete}>
+ {({ onClick }) => (
+ <Button
+ className="little-spacer-left button-red"
+ id="quality-gate-delete"
+ onClick={onClick}>
+ {translate('delete')}
+ </Button>
+ )}
+ </ConfirmButton>
);
}
}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import PropTypes from 'prop-types';
-import Helmet from 'react-helmet';
-import DetailsHeader from './DetailsHeader';
-import DetailsContent from './DetailsContent';
-import { fetchQualityGate } from '../../../api/quality-gates';
-
-export default class Details extends React.PureComponent {
- static contextTypes = {
- router: PropTypes.object.isRequired
- };
-
- componentDidMount() {
- this.props.fetchMetrics();
- this.fetchDetails();
- }
-
- componentDidUpdate(prevProps) {
- if (prevProps.params.id !== this.props.params.id) {
- this.fetchDetails();
- }
- }
-
- fetchDetails = () =>
- fetchQualityGate({
- id: this.props.params.id,
- organization: this.props.organization && this.props.organization.key
- }).then(qualityGate => this.props.onShow(qualityGate), () => {});
-
- render() {
- const { organization, metrics, qualityGate } = this.props;
- const { onAddCondition, onDeleteCondition, onSaveCondition } = this.props;
-
- if (!qualityGate) {
- return null;
- }
-
- return (
- <div className="layout-page-main">
- <Helmet title={qualityGate.name} />
- <DetailsHeader
- qualityGate={qualityGate}
- onRename={this.props.onRename}
- onCopy={this.props.onCopy}
- onSetAsDefault={this.props.onSetAsDefault}
- onDelete={this.props.onDelete}
- organization={organization && organization.key}
- />
-
- <DetailsContent
- gate={qualityGate}
- metrics={metrics}
- onAddCondition={onAddCondition}
- onSaveCondition={onSaveCondition}
- onDeleteCondition={onDeleteCondition}
- organization={organization && organization.key}
- />
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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';
+import * as PropTypes from 'prop-types';
+import Helmet from 'react-helmet';
+import { connect } from 'react-redux';
+import DetailsHeader from './DetailsHeader';
+import DetailsContent from './DetailsContent';
+import { getMetrics } from '../../../store/rootReducer';
+import { fetchMetrics } from '../../../store/rootActions';
+import { fetchQualityGate } from '../../../api/quality-gates';
+import { Metric, QualityGate, Condition } from '../../../app/types';
+import { checkIfDefault, addCondition, replaceCondition, deleteCondition } from '../utils';
+
+interface OwnProps {
+ onSetDefault: (qualityGate: QualityGate) => void;
+ organization?: string;
+ params: { id: number };
+ qualityGates: QualityGate[];
+ refreshQualityGates: () => Promise<void>;
+}
+
+interface StateToProps {
+ metrics: { [key: string]: Metric };
+}
+
+interface DispatchToProps {
+ fetchMetrics: () => void;
+}
+
+type Props = StateToProps & DispatchToProps & OwnProps;
+
+interface State {
+ loading: boolean;
+ qualityGate?: QualityGate;
+}
+
+export class DetailsApp extends React.PureComponent<Props, State> {
+ mounted = false;
+
+ static contextTypes = {
+ router: PropTypes.object.isRequired
+ };
+
+ state: State = { loading: true };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.props.fetchMetrics();
+ this.fetchDetails();
+ }
+
+ componentWillReceiveProps(nextProps: Props) {
+ if (nextProps.params.id !== this.props.params.id) {
+ this.setState({ loading: true });
+ this.fetchDetails(nextProps);
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchDetails = ({ organization, params } = this.props) => {
+ return fetchQualityGate({ id: params.id, organization }).then(
+ qualityGate => {
+ if (this.mounted) {
+ this.setState({ loading: false, qualityGate });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ };
+
+ handleAddCondition = (metric: string) => {
+ this.setState(({ qualityGate }) => {
+ if (!qualityGate) {
+ return undefined;
+ }
+ return { qualityGate: addCondition(qualityGate, metric) };
+ });
+ };
+
+ handleSaveCondition = (newCondition: Condition, oldCondition: Condition) => {
+ this.setState(({ qualityGate }) => {
+ if (!qualityGate) {
+ return undefined;
+ }
+ return { qualityGate: replaceCondition(qualityGate, newCondition, oldCondition) };
+ });
+ };
+
+ handleRemoveCondition = (condition: Condition) => {
+ this.setState(({ qualityGate }) => {
+ if (!qualityGate) {
+ return undefined;
+ }
+ return { qualityGate: deleteCondition(qualityGate, condition) };
+ });
+ };
+
+ handleSetDefault = () => {
+ this.setState(({ qualityGate }) => {
+ if (!qualityGate) {
+ return undefined;
+ }
+ this.props.onSetDefault(qualityGate);
+ const newQualityGate: QualityGate = {
+ ...qualityGate,
+ actions: { ...qualityGate.actions, delete: false, setAsDefault: false }
+ };
+ return { qualityGate: newQualityGate };
+ });
+ };
+
+ render() {
+ const { organization, metrics, refreshQualityGates } = this.props;
+ const { qualityGate } = this.state;
+
+ if (!qualityGate) {
+ return null;
+ }
+
+ return (
+ <>
+ <Helmet title={qualityGate.name} />
+ <div className="layout-page-main">
+ <DetailsHeader
+ onSetDefault={this.handleSetDefault}
+ organization={organization}
+ qualityGate={qualityGate}
+ refreshItem={this.fetchDetails}
+ refreshList={refreshQualityGates}
+ />
+ <DetailsContent
+ isDefault={checkIfDefault(qualityGate, this.props.qualityGates)}
+ metrics={metrics}
+ onAddCondition={this.handleAddCondition}
+ onRemoveCondition={this.handleRemoveCondition}
+ onSaveCondition={this.handleSaveCondition}
+ organization={organization}
+ qualityGate={qualityGate}
+ />
+ </div>
+ </>
+ );
+ }
+}
+
+const mapDispatchToProps: DispatchToProps = { fetchMetrics };
+
+const mapStateToProps = (state: any): StateToProps => ({
+ metrics: getMetrics(state)
+});
+
+export default connect<StateToProps, DispatchToProps, OwnProps>(
+ mapStateToProps,
+ mapDispatchToProps
+)(DetailsApp);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import Conditions from './Conditions';
-import Projects from './Projects';
-import DocTooltip from '../../../components/docs/DocTooltip';
-import { translate } from '../../../helpers/l10n';
-
-export default class DetailsContent extends React.PureComponent {
- render() {
- const { gate, metrics, organization } = this.props;
- const { onAddCondition, onDeleteCondition, onSaveCondition } = this.props;
- const conditions = gate.conditions || [];
- const actions = gate.actions || {};
-
- const defaultMessage = actions.associateProjects
- ? translate('quality_gates.projects_for_default.edit')
- : translate('quality_gates.projects_for_default');
-
- return (
- <div className="layout-page-main-inner">
- <Conditions
- qualityGate={gate}
- conditions={conditions}
- metrics={metrics}
- edit={actions.manageConditions}
- onAddCondition={onAddCondition}
- onSaveCondition={onSaveCondition}
- onDeleteCondition={onDeleteCondition}
- organization={organization}
- />
-
- <div id="quality-gate-projects" className="quality-gate-section">
- <header className="display-flex-center spacer-bottom">
- <h3>{translate('quality_gates.projects')}</h3>
- <DocTooltip className="spacer-left" doc="quality-gates/quality-gate-projects" />
- </header>
- {gate.isDefault ? (
- defaultMessage
- ) : (
- <Projects
- qualityGate={gate}
- edit={actions.associateProjects}
- organization={organization}
- />
- )}
- </div>
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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';
+import Conditions from './Conditions';
+import Projects from './Projects';
+import DocTooltip from '../../../components/docs/DocTooltip';
+import { translate } from '../../../helpers/l10n';
+import { Condition as ICondition, Metric, QualityGate } from '../../../app/types';
+
+interface Props {
+ isDefault?: boolean;
+ metrics: { [key: string]: Metric };
+ organization?: string;
+ onAddCondition: (metric: string) => void;
+ onRemoveCondition: (Condition: ICondition) => void;
+ onSaveCondition: (newCondition: ICondition, oldCondition: ICondition) => void;
+ qualityGate: QualityGate;
+}
+
+export default class DetailsContent extends React.PureComponent<Props> {
+ render() {
+ const { isDefault, metrics, organization, qualityGate } = this.props;
+ const conditions = qualityGate.conditions || [];
+ const actions = qualityGate.actions || ({} as any);
+
+ return (
+ <div className="layout-page-main-inner">
+ <Conditions
+ canEdit={actions.manageConditions}
+ conditions={conditions}
+ metrics={metrics}
+ onAddCondition={this.props.onAddCondition}
+ onRemoveCondition={this.props.onRemoveCondition}
+ onSaveCondition={this.props.onSaveCondition}
+ organization={organization}
+ qualityGate={qualityGate}
+ />
+
+ <div className="quality-gate-section" id="quality-gate-projects">
+ <header className="display-flex-center spacer-bottom">
+ <h3>{translate('quality_gates.projects')}</h3>
+ <DocTooltip className="spacer-left" doc="quality-gates/quality-gate-projects" />
+ </header>
+ {isDefault ? (
+ translate('quality_gates.projects_for_default')
+ ) : (
+ <Projects
+ canEdit={actions.associateProjects}
+ organization={organization}
+ qualityGate={qualityGate}
+ />
+ )}
+ </div>
+ </div>
+ );
+ }
+}
import RenameQualityGateForm from './RenameQualityGateForm';
import CopyQualityGateForm from './CopyQualityGateForm';
import DeleteQualityGateForm from './DeleteQualityGateForm';
-import { fetchQualityGate, QualityGate, setQualityGateAsDefault } from '../../../api/quality-gates';
+import { setQualityGateAsDefault } from '../../../api/quality-gates';
import { Button } from '../../../components/ui/buttons';
import { translate } from '../../../helpers/l10n';
+import { QualityGate } from '../../../app/types';
interface Props {
- qualityGate: QualityGate;
- onRename: (qualityGate: QualityGate, newName: string) => void;
- onCopy: (newQualityGate: QualityGate) => void;
- onSetAsDefault: (qualityGate: QualityGate) => void;
- onDelete: (qualityGate: QualityGate) => void;
organization?: string;
+ qualityGate: QualityGate;
+ onSetDefault: () => void;
+ refreshItem: () => Promise<void>;
+ refreshList: () => Promise<void>;
}
-interface State {
- openPopup?: string;
-}
-
-export default class DetailsHeader extends React.PureComponent<Props, State> {
- state = { openPopup: undefined };
-
- handleRenameClick = () => {
- this.setState({ openPopup: 'rename' });
- };
-
- handleCopyClick = () => {
- this.setState({ openPopup: 'copy' });
+export default class DetailsHeader extends React.PureComponent<Props> {
+ handleActionRefresh = () => {
+ const { refreshItem, refreshList } = this.props;
+ return Promise.all([refreshItem(), refreshList()]).then(() => {}, () => {});
};
handleSetAsDefaultClick = () => {
- const { qualityGate, onSetAsDefault, organization } = this.props;
+ const { organization, qualityGate } = this.props;
if (!qualityGate.isDefault) {
- setQualityGateAsDefault({ id: qualityGate.id, organization })
- .then(() => fetchQualityGate({ id: qualityGate.id, organization }))
- .then(qualityGate => onSetAsDefault(qualityGate), () => {});
+ // Optimistic update
+ this.props.onSetDefault();
+ setQualityGateAsDefault({ id: qualityGate.id, organization }).then(
+ this.handleActionRefresh,
+ this.handleActionRefresh
+ );
}
};
- handleDeleteClick = () => {
- this.setState({ openPopup: 'delete' });
- };
-
- handleClosePopup = () => this.setState({ openPopup: undefined });
-
render() {
const { organization, qualityGate } = this.props;
- const { openPopup } = this.state;
const actions = qualityGate.actions || ({} as any);
return (
<div className="layout-page-header-panel layout-page-main-header issues-main-header">
<div className="pull-right">
{actions.rename && (
- <Button id="quality-gate-rename" onClick={this.handleRenameClick}>
- {translate('rename')}
- </Button>
+ <RenameQualityGateForm
+ onRename={this.handleActionRefresh}
+ organization={organization}
+ qualityGate={qualityGate}
+ />
)}
{actions.copy && (
- <Button
- className="little-spacer-left"
- id="quality-gate-copy"
- onClick={this.handleCopyClick}>
- {translate('copy')}
- </Button>
+ <CopyQualityGateForm
+ onCopy={this.handleActionRefresh}
+ organization={organization}
+ qualityGate={qualityGate}
+ />
)}
{actions.setAsDefault && (
<Button
</Button>
)}
{actions.delete && (
- <Button
- className="little-spacer-left button-red"
- id="quality-gate-delete"
- onClick={this.handleDeleteClick}>
- {translate('delete')}
- </Button>
- )}
- {openPopup === 'rename' && (
- <RenameQualityGateForm
- onClose={this.handleClosePopup}
- onRename={this.props.onRename}
- organization={organization}
- qualityGate={qualityGate}
- />
- )}
-
- {openPopup === 'copy' && (
- <CopyQualityGateForm
- onClose={this.handleClosePopup}
- onCopy={this.props.onCopy}
- organization={organization}
- qualityGate={qualityGate}
- />
- )}
-
- {openPopup === 'delete' && (
<DeleteQualityGateForm
- onClose={this.handleClosePopup}
- onDelete={this.props.onDelete}
+ onDelete={this.handleActionRefresh}
organization={organization}
qualityGate={qualityGate}
/>
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { translate } from '../../../helpers/l10n';
-
-export default function Intro() {
- return (
- <div className="layout-page-main">
- <div className="layout-page-main-inner">
- <div className="search-navigator-intro markdown">
- <p>{translate('quality_gates.intro.1')}</p>
- <p>{translate('quality_gates.intro.2')}</p>
- </div>
- </div>
- </div>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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';
+import { translate } from '../../../helpers/l10n';
+
+export default function Intro() {
+ return (
+ <div className="layout-page-main">
+ <div className="layout-page-main-inner">
+ <div className="search-navigator-intro markdown">
+ <p>{translate('quality_gates.intro.1')}</p>
+ <p>{translate('quality_gates.intro.2')}</p>
+ </div>
+ </div>
+ </div>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { Link } from 'react-router';
-import BuiltInQualityGateBadge from './BuiltInQualityGateBadge';
-import { translate } from '../../../helpers/l10n';
-import { getQualityGateUrl } from '../../../helpers/urls';
-
-export default function List({ organization, qualityGates }) {
- return (
- <div className="list-group">
- {qualityGates.map(qualityGate => (
- <Link
- activeClassName="active"
- className="list-group-item"
- data-id={qualityGate.id}
- key={qualityGate.id}
- to={getQualityGateUrl(String(qualityGate.id), organization && organization.key)}>
- <table>
- <tbody>
- <tr>
- <td>{qualityGate.name}</td>
- <td className="thin nowrap spacer-left text-right">
- {qualityGate.isDefault && <span className="badge">{translate('default')}</span>}
- {qualityGate.isBuiltIn && (
- <BuiltInQualityGateBadge className="little-spacer-left" />
- )}
- </td>
- </tr>
- </tbody>
- </table>
- </Link>
- ))}
- </div>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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';
+import { Link } from 'react-router';
+import BuiltInQualityGateBadge from './BuiltInQualityGateBadge';
+import { translate } from '../../../helpers/l10n';
+import { getQualityGateUrl } from '../../../helpers/urls';
+import { QualityGate } from '../../../app/types';
+
+interface Props {
+ organization?: string;
+ qualityGates: QualityGate[];
+}
+
+export default function List({ organization, qualityGates }: Props) {
+ return (
+ <div className="list-group">
+ {qualityGates.map(qualityGate => (
+ <Link
+ activeClassName="active"
+ className="list-group-item"
+ data-id={qualityGate.id}
+ key={qualityGate.id}
+ to={getQualityGateUrl(String(qualityGate.id), organization)}>
+ <table>
+ <tbody>
+ <tr>
+ <td>{qualityGate.name}</td>
+ <td className="thin nowrap spacer-left text-right">
+ {qualityGate.isDefault && <span className="badge">{translate('default')}</span>}
+ {qualityGate.isBuiltIn && (
+ <BuiltInQualityGateBadge className="little-spacer-left" />
+ )}
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </Link>
+ ))}
+ </div>
+ );
+}
*/
import * as React from 'react';
import CreateQualityGateForm from '../components/CreateQualityGateForm';
-import { QualityGate } from '../../../api/quality-gates';
-import { Organization } from '../../../app/types';
import DocTooltip from '../../../components/docs/DocTooltip';
-import { Button } from '../../../components/ui/buttons';
import { translate } from '../../../helpers/l10n';
interface Props {
canCreate: boolean;
- onAdd: (qualityGate: QualityGate) => void;
- organization?: Organization;
+ refreshQualityGates: () => Promise<void>;
+ organization?: string;
}
-interface State {
- createQualityGateOpen: boolean;
-}
-
-export default class ListHeader extends React.PureComponent<Props, State> {
- state = { createQualityGateOpen: false };
-
- openCreateQualityGateForm = () => {
- this.setState({ createQualityGateOpen: true });
- };
-
- closeCreateQualityGateForm = () => {
- this.setState({ createQualityGateOpen: false });
- };
-
- render() {
- const { organization } = this.props;
-
- return (
- <header className="page-header">
- {this.props.canCreate && (
- <div className="page-actions">
- <Button id="quality-gate-add" onClick={this.openCreateQualityGateForm}>
- {translate('create')}
- </Button>
- </div>
- )}
- <div className="display-flex-center">
- <h1 className="page-title">{translate('quality_gates.page')}</h1>
- <DocTooltip className="spacer-left" doc="quality-gates/quality-gate" />
+export default function ListHeader({ canCreate, refreshQualityGates, organization }: Props) {
+ return (
+ <header className="page-header">
+ {canCreate && (
+ <div className="page-actions">
+ <CreateQualityGateForm onCreate={refreshQualityGates} organization={organization} />
</div>
- {this.state.createQualityGateOpen && (
- <CreateQualityGateForm
- onClose={this.closeCreateQualityGateForm}
- onCreate={this.props.onAdd}
- organization={organization && organization.key}
- />
- )}
- </header>
- );
- }
+ )}
+
+ <div className="display-flex-center">
+ <h1 className="page-title">{translate('quality_gates.page')}</h1>
+ <DocTooltip className="spacer-left" doc="quality-gates/quality-gate" />
+ </div>
+ </header>
+ );
}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { find, without } from 'lodash';
-import SelectList, { Filter } from '../../../components/SelectList/SelectList';
-import { translate } from '../../../helpers/l10n';
-import {
- searchGates,
- associateGateWithProject,
- dissociateGateWithProject
-} from '../../../api/quality-gates';
-/*:: import { Project } from '../../projects/types'; */
-
-/*::
-type State = {
- projects: Projects[],
- selectedProjects: string[]
-};
-*/
-
-export default class Projects extends React.PureComponent {
- state /*: State */ = { projects: [], selectedProjects: [] };
-
- componentDidMount() {
- this.handleSearch('', Filter.Selected);
- }
-
- handleSearch = (query /*: string*/, selected /*: string */) => {
- return searchGates({
- gateId: this.props.qualityGate.id,
- organization: this.props.organization,
- pageSize: 100,
- query: query !== '' ? query : undefined,
- selected
- }).then(data => {
- this.setState({
- projects: data.results,
- selectedProjects: data.results
- .filter(project => project.selected)
- .map(project => project.id)
- });
- });
- };
-
- handleSelect = (id /*: string*/) => {
- return associateGateWithProject({
- gateId: this.props.qualityGate.id,
- organization: this.props.organization,
- projectId: id
- }).then(() => {
- this.setState((state /*: State*/) => ({
- selectedProjects: [...state.selectedProjects, id]
- }));
- });
- };
-
- handleUnselect = (id /*: string*/) => {
- return dissociateGateWithProject({
- gateId: this.props.qualityGate.id,
- organization: this.props.organization,
- projectId: id
- }).then(
- () => {
- this.setState((state /*: State*/) => ({
- selectedProjects: without(state.selectedProjects, id)
- }));
- },
- () => {}
- );
- };
-
- renderElement = (id /*: string*/) /*: React.ReactNode*/ => {
- const project = find(this.state.projects, { id });
- return project === undefined ? id : project.name;
- };
-
- render() {
- return (
- <SelectList
- elements={this.state.projects.map(project => project.id)}
- labelAll={translate('quality_gates.projects.all')}
- labelSelected={translate('quality_gates.projects.with')}
- labelUnselected={translate('quality_gates.projects.without')}
- onSearch={this.handleSearch}
- onSelect={this.handleSelect}
- onUnselect={this.handleUnselect}
- renderElement={this.renderElement}
- selectedElements={this.state.selectedProjects}
- />
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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';
+import { find, without } from 'lodash';
+import SelectList, { Filter } from '../../../components/SelectList/SelectList';
+import { translate } from '../../../helpers/l10n';
+import {
+ searchGates,
+ associateGateWithProject,
+ dissociateGateWithProject
+} from '../../../api/quality-gates';
+import { QualityGate } from '../../../app/types';
+
+interface Props {
+ canEdit?: boolean;
+ organization?: string;
+ qualityGate: QualityGate;
+}
+
+interface State {
+ projects: Array<{ id: string; name: string; selected: boolean }>;
+ selectedProjects: string[];
+}
+
+export default class Projects extends React.PureComponent<Props, State> {
+ state: State = { projects: [], selectedProjects: [] };
+
+ componentDidMount() {
+ this.handleSearch('', Filter.Selected);
+ }
+
+ handleSearch = (query: string, selected: string) => {
+ return searchGates({
+ gateId: this.props.qualityGate.id,
+ organization: this.props.organization,
+ pageSize: 100,
+ query: query !== '' ? query : undefined,
+ selected
+ }).then(data => {
+ this.setState({
+ projects: data.results,
+ selectedProjects: data.results
+ .filter(project => project.selected)
+ .map(project => project.id)
+ });
+ });
+ };
+
+ handleSelect = (id: string) => {
+ return associateGateWithProject({
+ gateId: this.props.qualityGate.id,
+ organization: this.props.organization,
+ projectId: id
+ }).then(() => {
+ this.setState(state => ({
+ selectedProjects: [...state.selectedProjects, id]
+ }));
+ });
+ };
+
+ handleUnselect = (id: string) => {
+ return dissociateGateWithProject({
+ gateId: this.props.qualityGate.id,
+ organization: this.props.organization,
+ projectId: id
+ }).then(
+ () => {
+ this.setState(state => ({
+ selectedProjects: without(state.selectedProjects, id)
+ }));
+ },
+ () => {}
+ );
+ };
+
+ renderElement = (id: string): React.ReactNode => {
+ const project = find(this.state.projects, { id });
+ return project === undefined ? id : project.name;
+ };
+
+ render() {
+ return (
+ <SelectList
+ elements={this.state.projects.map(project => project.id)}
+ labelAll={translate('quality_gates.projects.all')}
+ labelSelected={translate('quality_gates.projects.with')}
+ labelUnselected={translate('quality_gates.projects.without')}
+ onSearch={this.handleSearch}
+ onSelect={this.handleSelect}
+ onUnselect={this.handleUnselect}
+ readOnly={!this.props.canEdit}
+ renderElement={this.renderElement}
+ selectedElements={this.state.selectedProjects}
+ />
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import Helmet from 'react-helmet';
-import ListHeader from './ListHeader';
-import List from './List';
-import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
-import { fetchQualityGates } from '../../../api/quality-gates';
-import { translate } from '../../../helpers/l10n';
-import { getQualityGateUrl } from '../../../helpers/urls';
-import '../styles.css';
-
-export default class QualityGatesApp extends Component {
- static contextTypes = {
- router: PropTypes.object.isRequired
- };
-
- state = {};
-
- componentDidMount() {
- this.fetchQualityGates();
- // $FlowFixMe
- document.body.classList.add('white-page');
- // $FlowFixMe
- document.documentElement.classList.add('white-page');
- const footer = document.getElementById('footer');
- if (footer) {
- footer.classList.add('page-footer-with-sidebar');
- }
- }
-
- componentWillUnmount() {
- // $FlowFixMe
- document.body.classList.remove('white-page');
- // $FlowFixMe
- document.documentElement.classList.remove('white-page');
- const footer = document.getElementById('footer');
- if (footer) {
- footer.classList.remove('page-footer-with-sidebar');
- }
- }
-
- fetchQualityGates = () =>
- fetchQualityGates({
- organization: this.props.organization && this.props.organization.key
- }).then(
- ({ actions, qualitygates: qualityGates }) => {
- const { organization, updateStore } = this.props;
- updateStore({ actions, qualityGates });
- if (qualityGates && qualityGates.length === 1 && !actions.create) {
- this.context.router.replace(
- getQualityGateUrl(String(qualityGates[0].id), organization && organization.key)
- );
- }
- },
- () => {}
- );
-
- render() {
- const { children, qualityGates, actions, organization } = this.props;
- const defaultTitle = translate('quality_gates.page');
- return (
- <div id="quality-gates-page" className="layout-page">
- <Suggestions suggestions="quality_gates" />
- <Helmet defaultTitle={defaultTitle} titleTemplate={'%s - ' + defaultTitle} />
-
- <ScreenPositionHelper className="layout-page-side-outer">
- {({ top }) => (
- <div className="layout-page-side" style={{ top }}>
- <div className="layout-page-side-inner">
- <div className="layout-page-filters">
- <ListHeader
- canCreate={actions && actions.create}
- onAdd={this.props.addQualityGate}
- organization={organization}
- />
- {qualityGates && <List organization={organization} qualityGates={qualityGates} />}
- </div>
- </div>
- </div>
- )}
- </ScreenPositionHelper>
- {qualityGates != null &&
- React.Children.map(children, child => React.cloneElement(child, { organization }))}
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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';
+import * as PropTypes from 'prop-types';
+import Helmet from 'react-helmet';
+import ListHeader from './ListHeader';
+import List from './List';
+import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
+import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import { fetchQualityGates } from '../../../api/quality-gates';
+import { translate } from '../../../helpers/l10n';
+import { getQualityGateUrl } from '../../../helpers/urls';
+import { Organization, QualityGate } from '../../../app/types';
+import '../styles.css';
+
+interface Props {
+ children: React.ReactElement<{
+ organization?: string;
+ refreshQualityGates: () => Promise<void>;
+ }>;
+ organization: Pick<Organization, 'key'>;
+}
+
+interface State {
+ canCreate: boolean;
+ loading: boolean;
+ qualityGates: QualityGate[];
+}
+
+export default class QualityGatesApp extends React.PureComponent<Props, State> {
+ mounted = false;
+
+ static contextTypes = {
+ router: PropTypes.object.isRequired
+ };
+
+ state: State = { canCreate: false, loading: true, qualityGates: [] };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchQualityGates();
+
+ document.body.classList.add('white-page');
+ document.documentElement.classList.add('white-page');
+ const footer = document.getElementById('footer');
+ if (footer) {
+ footer.classList.add('page-footer-with-sidebar');
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ document.body.classList.remove('white-page');
+ document.documentElement.classList.remove('white-page');
+ const footer = document.getElementById('footer');
+ if (footer) {
+ footer.classList.remove('page-footer-with-sidebar');
+ }
+ }
+
+ fetchQualityGates = () => {
+ const { organization } = this.props;
+ return fetchQualityGates({ organization: organization && organization.key }).then(
+ ({ actions, qualitygates: qualityGates }) => {
+ if (this.mounted) {
+ this.setState({ canCreate: actions.create, loading: false, qualityGates });
+
+ if (qualityGates && qualityGates.length === 1 && !actions.create) {
+ this.context.router.replace(
+ getQualityGateUrl(String(qualityGates[0].id), organization && organization.key)
+ );
+ }
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ };
+
+ handleSetDefault = (qualityGate: QualityGate) => {
+ this.setState(({ qualityGates }) => {
+ return {
+ qualityGates: qualityGates.map(candidate => {
+ if (candidate.isDefault || candidate.id === qualityGate.id) {
+ return { ...candidate, isDefault: candidate.id === qualityGate.id };
+ }
+ return candidate;
+ })
+ };
+ });
+ };
+
+ render() {
+ const { children } = this.props;
+ const { canCreate, loading, qualityGates } = this.state;
+ const defaultTitle = translate('quality_gates.page');
+ const organization = this.props.organization && this.props.organization.key;
+
+ return (
+ <>
+ <Helmet defaultTitle={defaultTitle} titleTemplate={'%s - ' + defaultTitle} />
+ <div className="layout-page" id="quality-gates-page">
+ <Suggestions suggestions="quality_gates" />
+
+ <ScreenPositionHelper className="layout-page-side-outer">
+ {({ top }) => (
+ <div className="layout-page-side" style={{ top }}>
+ <div className="layout-page-side-inner">
+ <div className="layout-page-filters">
+ <ListHeader
+ canCreate={canCreate}
+ organization={organization}
+ refreshQualityGates={this.fetchQualityGates}
+ />
+ {qualityGates.length > 0 && (
+ <List organization={organization} qualityGates={qualityGates} />
+ )}
+ </div>
+ </div>
+ </div>
+ )}
+ </ScreenPositionHelper>
+ {!loading &&
+ React.cloneElement(children, {
+ onSetDefault: this.handleSetDefault,
+ organization,
+ qualityGates,
+ refreshQualityGates: this.fetchQualityGates
+ })}
+ </div>
+ </>
+ );
+ }
+}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { QualityGate, renameQualityGate } from '../../../api/quality-gates';
-import Modal from '../../../components/controls/Modal';
-import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons';
+import ConfirmButton from '../../../components/controls/ConfirmButton';
+import { renameQualityGate } from '../../../api/quality-gates';
+import { Button } from '../../../components/ui/buttons';
import { translate } from '../../../helpers/l10n';
+import { QualityGate } from '../../../app/types';
interface Props {
- qualityGate: QualityGate;
- onRename: (qualityGate: QualityGate, newName: string) => void;
- onClose: () => void;
+ onRename: () => Promise<void>;
organization?: string;
+ qualityGate: QualityGate;
}
interface State {
- loading: boolean;
name: string;
}
export default class RenameQualityGateForm extends React.PureComponent<Props, State> {
- mounted = false;
-
constructor(props: Props) {
super(props);
- this.state = { loading: false, name: props.qualityGate.name };
- }
-
- componentDidMount() {
- this.mounted = true;
- }
-
- componentWillUnmount() {
- this.mounted = false;
+ this.state = { name: props.qualityGate.name };
}
- handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => {
+ handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ name: event.currentTarget.value });
};
- handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
- event.preventDefault();
+ onRename = () => {
const { qualityGate, organization } = this.props;
const { name } = this.state;
- if (name) {
- this.setState({ loading: true });
- renameQualityGate({ id: qualityGate.id, name, organization }).then(
- () => {
- this.props.onRename(qualityGate, name);
- this.props.onClose();
- },
- () => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
- }
- );
+
+ if (!name) {
+ return undefined;
}
+
+ return renameQualityGate({ id: qualityGate.id, name, organization }).then(() =>
+ this.props.onRename()
+ );
};
render() {
const { qualityGate } = this.props;
- const { loading, name } = this.state;
- const header = translate('quality_gates.rename');
- const submitDisabled = loading || !name || (qualityGate && qualityGate.name === name);
+ const { name } = this.state;
+ const confirmDisable = !name || (qualityGate && qualityGate.name === name);
return (
- <Modal contentLabel={header} onRequestClose={this.props.onClose}>
- <form id="quality-gate-form" onSubmit={this.handleFormSubmit}>
- <div className="modal-head">
- <h2>{header}</h2>
+ <ConfirmButton
+ confirmButtonText={translate('rename')}
+ confirmDisable={confirmDisable}
+ modalBody={
+ <div className="modal-field">
+ <label htmlFor="quality-gate-form-name">
+ {translate('name')}
+ <em className="mandatory">*</em>
+ </label>
+ <input
+ autoFocus={true}
+ id="quality-gate-form-name"
+ maxLength={100}
+ onChange={this.handleNameChange}
+ required={true}
+ size={50}
+ type="text"
+ value={name}
+ />
</div>
- <div className="modal-body">
- <div className="modal-field">
- <label htmlFor="quality-gate-form-name">
- {translate('name')}
- <em className="mandatory">*</em>
- </label>
- <input
- autoFocus={true}
- id="quality-gate-form-name"
- maxLength={100}
- onChange={this.handleNameChange}
- required={true}
- size={50}
- type="text"
- value={name}
- />
- </div>
- </div>
- <div className="modal-foot">
- {loading && <i className="spinner spacer-right" />}
- <SubmitButton className="js-confirm" disabled={submitDisabled}>
- {translate('rename')}
- </SubmitButton>
- <ResetButtonLink className="js-modal-close" onClick={this.props.onClose}>
- {translate('cancel')}
- </ResetButtonLink>
- </div>
- </form>
- </Modal>
+ }
+ modalHeader={translate('quality_gates.rename')}
+ onConfirm={this.onRename}>
+ {({ onClick }) => (
+ <Button id="quality-gate-rename" onClick={onClick}>
+ {translate('rename')}
+ </Button>
+ )}
+ </ConfirmButton>
);
}
}
return (
<input
- name={name}
- type="text"
className="input-tiny text-middle"
- value={value}
data-type={metric.type}
+ name={name}
onChange={this.handleChange}
+ type="text"
+ value={value}
/>
);
}
const metric = { id: '1', key: 'foo', name: 'Foo', type: 'INTEGER' };
it('should render text input', () => {
const input = shallow(
- <ThresholdInput name="foo" value="2" metric={metric} onChange={jest.fn()} />
+ <ThresholdInput metric={metric} name="foo" onChange={jest.fn()} value="2" />
).find('input');
expect(input.length).toEqual(1);
expect(input.prop('name')).toEqual('foo');
it('should change', () => {
const onChange = jest.fn();
const input = shallow(
- <ThresholdInput name="foo" value="2" metric={metric} onChange={onChange} />
+ <ThresholdInput metric={metric} name="foo" onChange={onChange} value="2" />
).find('input');
change(input, 'bar');
expect(onChange).toBeCalledWith('bar');
const metric = { id: '1', key: 'foo', name: 'Foo', type: 'RATING' };
it('should render Select', () => {
const select = shallow(
- <ThresholdInput name="foo" value="2" metric={metric} onChange={jest.fn()} />
+ <ThresholdInput metric={metric} name="foo" onChange={jest.fn()} value="2" />
).find('Select');
expect(select.length).toEqual(1);
expect(select.prop('value')).toEqual('2');
it('should set', () => {
const onChange = jest.fn();
const select = shallow(
- <ThresholdInput name="foo" value="2" metric={metric} onChange={onChange} />
+ <ThresholdInput metric={metric} name="foo" onChange={onChange} value="2" />
).find('Select');
(select.prop('onChange') as Function)({ label: 'D', value: '4' });
expect(onChange).toBeCalledWith('4');
it('should unset', () => {
const onChange = jest.fn();
const select = shallow(
- <ThresholdInput name="foo" value="2" metric={metric} onChange={onChange} />
+ <ThresholdInput metric={metric} name="foo" onChange={onChange} value="2" />
).find('Select');
(select.prop('onChange') as Function)(null);
expect(onChange).toBeCalledWith('');
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 { connect } from 'react-redux';
-import {
- deleteQualityGate,
- showQualityGate,
- renameQualityGate,
- copyQualityGate,
- setQualityGateAsDefault,
- addCondition,
- deleteCondition,
- saveCondition
-} from '../store/actions';
-import Details from '../components/Details';
-import { getMetrics, getQualityGatesAppState } from '../../../store/rootReducer';
-import { fetchMetrics } from '../../../store/rootActions';
-
-const mapStateToProps = state => ({
- ...getQualityGatesAppState(state),
- metrics: getMetrics(state)
-});
-
-const mapDispatchToProps = dispatch => ({
- onShow: qualityGate => dispatch(showQualityGate(qualityGate)),
- onDelete: qualityGate => dispatch(deleteQualityGate(qualityGate)),
- onRename: (qualityGate, newName) => dispatch(renameQualityGate(qualityGate, newName)),
- onCopy: qualityGate => dispatch(copyQualityGate(qualityGate)),
- onSetAsDefault: qualityGate => dispatch(setQualityGateAsDefault(qualityGate)),
- onAddCondition: metric => dispatch(addCondition(metric)),
- onSaveCondition: (oldCondition, newCondition) =>
- dispatch(saveCondition(oldCondition, newCondition)),
- onDeleteCondition: condition => dispatch(deleteCondition(condition)),
- fetchMetrics: () => dispatch(fetchMetrics())
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(Details);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 { connect } from 'react-redux';
-import { setState, addQualityGate, deleteQualityGate } from '../store/actions';
-import QualityGateApp from '../components/QualityGatesApp';
-import { getQualityGatesAppState } from '../../../store/rootReducer';
-
-const mapStateToProps = state => getQualityGatesAppState(state);
-
-const mapDispatchToProps = dispatch => ({
- updateStore: nextState => dispatch(setState(nextState)),
- addQualityGate: qualityGate => dispatch(addQualityGate(qualityGate)),
- deleteQualityGate: qualityGate => dispatch(deleteQualityGate(qualityGate))
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(QualityGateApp);
const routes = [
{
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
- import('./containers/QualityGatesAppContainer').then(i => callback(null, i.default));
+ import('./components/QualityGatesApp').then(i => callback(null, i.default));
},
childRoutes: [
{
{
path: 'show/:id',
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
- import('./containers/DetailsContainer').then(i => callback(null, i.default));
+ import('./components/DetailsApp').then(i => callback(null, i.default));
}
}
]
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-export const SET_STATE = 'qualityGates/SET_STATE';
-export function setState(nextState) {
- return {
- type: SET_STATE,
- nextState
- };
-}
-
-export const ADD = 'qualityGates/ADD';
-export function addQualityGate(qualityGate) {
- return {
- type: ADD,
- qualityGate
- };
-}
-
-export const DELETE = 'qualityGates/DELETE';
-export function deleteQualityGate(qualityGate) {
- return {
- type: DELETE,
- qualityGate
- };
-}
-
-export const SHOW = 'qualityGates/SHOW';
-export function showQualityGate(qualityGate) {
- return {
- type: SHOW,
- qualityGate
- };
-}
-
-export const RENAME = 'qualityGates/RENAME';
-export function renameQualityGate(qualityGate, newName) {
- return {
- type: RENAME,
- qualityGate,
- newName
- };
-}
-
-export const COPY = 'qualityGates/COPY';
-export function copyQualityGate(qualityGate) {
- return {
- type: COPY,
- qualityGate
- };
-}
-
-export const SET_AS_DEFAULT = 'qualityGates/SET_AS_DEFAULT';
-export function setQualityGateAsDefault(qualityGate) {
- return {
- type: SET_AS_DEFAULT,
- qualityGate
- };
-}
-
-export const ADD_CONDITION = 'qualityGates/ADD_CONDITION';
-export function addCondition(metric) {
- return {
- type: ADD_CONDITION,
- metric
- };
-}
-
-export const SAVE_CONDITION = 'qualityGates/SAVE_CONDITION';
-export function saveCondition(oldCondition, newCondition) {
- return {
- type: SAVE_CONDITION,
- oldCondition,
- newCondition
- };
-}
-
-export const DELETE_CONDITION = 'qualityGates/DELETE_CONDITION';
-export function deleteCondition(condition) {
- return {
- type: DELETE_CONDITION,
- condition
- };
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 {
- SET_STATE,
- ADD,
- DELETE,
- SHOW,
- RENAME,
- COPY,
- SET_AS_DEFAULT,
- ADD_CONDITION,
- DELETE_CONDITION,
- SAVE_CONDITION
-} from './actions';
-import { checkIfDefault, addCondition, deleteCondition, replaceCondition } from './utils';
-
-const initialState = {};
-
-export default function rootReducer(state = initialState, action = {}) {
- switch (action.type) {
- case SET_STATE:
- return { ...state, ...action.nextState };
- case ADD:
- case COPY:
- return { ...state, qualityGates: [...state.qualityGates, action.qualityGate] };
- case DELETE:
- return {
- ...state,
- qualityGates: state.qualityGates.filter(
- candidate => candidate.id !== action.qualityGate.id
- ),
- qualityGate: state.qualityGate.id === action.qualityGate.id ? undefined : state.qualityGate
- };
- case SHOW:
- return {
- ...state,
- qualityGate: {
- ...action.qualityGate,
- isDefault: checkIfDefault(action.qualityGate, state.qualityGates)
- }
- };
- case RENAME:
- return {
- ...state,
- qualityGates: state.qualityGates.map(candidate => {
- return candidate.id === action.qualityGate.id
- ? { ...candidate, name: action.newName }
- : candidate;
- }),
- qualityGate: { ...state.qualityGate, name: action.newName }
- };
- case SET_AS_DEFAULT:
- return {
- ...state,
- qualityGates: state.qualityGates.map(candidate => {
- return { ...candidate, isDefault: candidate.id === action.qualityGate.id };
- }),
- qualityGate: {
- ...action.qualityGate,
- isDefault: state.qualityGate.id === action.qualityGate.id
- }
- };
- case ADD_CONDITION:
- return {
- ...state,
- qualityGate: addCondition(state.qualityGate, action.metric)
- };
- case DELETE_CONDITION:
- return {
- ...state,
- qualityGate: deleteCondition(state.qualityGate, action.condition)
- };
- case SAVE_CONDITION:
- return {
- ...state,
- qualityGate: replaceCondition(state.qualityGate, action.oldCondition, action.newCondition)
- };
- default:
- return state;
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-export function checkIfDefault(qualityGate, list) {
- const finding = list.find(candidate => candidate.id === qualityGate.id);
-
- return finding ? finding.isDefault : false;
-}
-
-export function addCondition(qualityGate, metric) {
- const condition = {
- metric,
- op: 'LT',
- warning: '',
- error: ''
- };
- const oldConditions = qualityGate.conditions || [];
- const conditions = [...oldConditions, condition];
-
- return { ...qualityGate, conditions };
-}
-
-export function deleteCondition(qualityGate, condition) {
- const conditions = qualityGate.conditions.filter(candidate => candidate !== condition);
-
- return { ...qualityGate, conditions };
-}
-
-export function replaceCondition(qualityGate, oldCondition, newCondition) {
- const conditions = qualityGate.conditions.map(candidate => {
- return candidate === oldCondition ? newCondition : candidate;
- });
- return { ...qualityGate, conditions };
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { QualityGate, Condition } from '../../app/types';
+
+export function checkIfDefault(qualityGate: QualityGate, list: QualityGate[]): boolean {
+ const finding = list.find(candidate => candidate.id === qualityGate.id);
+ return (finding && finding.isDefault) || false;
+}
+
+export function addCondition(qualityGate: QualityGate, metric: string): QualityGate {
+ const condition: Condition = {
+ metric,
+ op: 'LT',
+ warning: '',
+ error: ''
+ };
+ const oldConditions = qualityGate.conditions || [];
+ const conditions = [...oldConditions, condition];
+ return { ...qualityGate, conditions };
+}
+
+export function deleteCondition(qualityGate: QualityGate, condition: Condition): QualityGate {
+ const conditions =
+ qualityGate.conditions && qualityGate.conditions.filter(candidate => candidate !== condition);
+ return { ...qualityGate, conditions };
+}
+
+export function replaceCondition(
+ qualityGate: QualityGate,
+ newCondition: Condition,
+ oldCondition: Condition
+): QualityGate {
+ const conditions =
+ qualityGate.conditions &&
+ qualityGate.conditions.map(candidate => {
+ return candidate === oldCondition ? newCondition : candidate;
+ });
+ return { ...qualityGate, conditions };
+}
render() {
return (
- <li>
+ <li className={classNames({ 'select-list-list-disabled': this.props.disabled })}>
<Checkbox
checked={this.props.selected}
className={classNames('select-list-list-checkbox', { active: this.props.active })}
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should display a loader when checking 1`] = `
-<li>
+<li
+ className=""
+>
<Checkbox
checked={false}
className="select-list-list-checkbox"
`;
exports[`should display a loader when checking 2`] = `
-<li>
+<li
+ className=""
+>
<Checkbox
checked={false}
className="select-list-list-checkbox"
margin-right: 10px;
}
+.select-list-list-disabled {
+ cursor: not-allowed;
+}
+
+.select-list-list-disabled > a {
+ pointer-events: none;
+}
+
.select-list-list-item {
display: inline-block;
vertical-align: middle;
children: (props: ChildrenProps) => React.ReactNode;
confirmButtonText: string;
confirmData?: string;
+ confirmDisable?: boolean;
isDestructive?: boolean;
modalBody: React.ReactNode;
modalHeader: string;
};
render() {
- const { confirmButtonText, isDestructive, modalBody, modalHeader } = this.props;
+ const { confirmButtonText, confirmDisable, isDestructive, modalBody, modalHeader } = this.props;
return (
<>
<DeferredSpinner className="spacer-right" loading={submitting} />
<SubmitButton
className={isDestructive ? 'button-red' : undefined}
- disabled={submitting}>
+ disabled={submitting || confirmDisable}>
{confirmButtonText}
</SubmitButton>
<ResetButtonLink disabled={submitting} onClick={onCloseClick}>
import globalMessages, * as fromGlobalMessages from './globalMessages/duck';
import permissionsApp, * as fromPermissionsApp from '../apps/permissions/shared/store/rootReducer';
import projectAdminApp, * as fromProjectAdminApp from '../apps/project-admin/store/rootReducer';
-import qualityGatesApp from '../apps/quality-gates/store/rootReducer';
import settingsApp, * as fromSettingsApp from '../apps/settings/store/rootReducer';
export default combineReducers({
// apps
permissionsApp,
projectAdminApp,
- qualityGatesApp,
settingsApp
});
export const getOrganizationMembersState = (state, organization) =>
fromOrganizationsMembers.getOrganizationMembersState(state.organizationsMembers, organization);
-export const getQualityGatesAppState = state => state.qualityGatesApp;
-
export const getPermissionsAppUsers = state => fromPermissionsApp.getUsers(state.permissionsApp);
export const getPermissionsAppGroups = state => fromPermissionsApp.getGroups(state.permissionsApp);
quality_gates.introduction=Only project measures are checked against thresholds. Sub-projects, directories and files are ignored.
quality_gates.health_icons=Project health icons represent:
quality_gates.projects_for_default=Every project not specifically associated to a quality gate will be associated to this one by default.
-quality_gates.projects_for_default.edit=You must not select specific projects for the default quality gate.
quality_gates.projects.with=With
quality_gates.projects.without=Without
quality_gates.projects.all=All