diff options
author | Revanshu Paliwal <revanshu.paliwal@sonarsource.com> | 2023-09-11 17:46:51 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-09-19 20:02:46 +0000 |
commit | bd404df9f5df0ce8fb2119c1dc27fde282c82b5a (patch) | |
tree | 3d8e4705cf06996fad5afeb8fdba3bd1f35c07ae | |
parent | 44e4e489ee6fd262d805a0418bd381625aaee349 (diff) | |
download | sonarqube-bd404df9f5df0ce8fb2119c1dc27fde282c82b5a.tar.gz sonarqube-bd404df9f5df0ce8fb2119c1dc27fde282c82b5a.zip |
SONAR-20337 Updating modals for quality gate's conditions
9 files changed, 282 insertions, 221 deletions
diff --git a/server/sonar-web/design-system/src/components/modal/Modal.tsx b/server/sonar-web/design-system/src/components/modal/Modal.tsx index 01a9163178d..4746501fe59 100644 --- a/server/sonar-web/design-system/src/components/modal/Modal.tsx +++ b/server/sonar-web/design-system/src/components/modal/Modal.tsx @@ -90,7 +90,7 @@ export function Modal({ <Global styles={globalStyles({ theme })} /> <ReactModal - aria={{ labelledby: '#modal_header_title' }} + aria={{ labelledby: 'modal_header_title' }} className={classNames('design-system-modal-contents modal', { large: isLarge })} isOpen={isOpen} onRequestClose={onClose} @@ -147,6 +147,8 @@ const globalStyles = ({ theme }: { theme: Theme }) => css` &.large { max-width: 1280px; min-width: 1040px; + transform: translateX(-50%); + margin-left: 0px; } } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx index 5623df94098..956249dafd2 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx @@ -21,8 +21,10 @@ import classNames from 'classnames'; import { ActionCell, ContentCell, + DangerButtonPrimary, DestructiveIcon, InteractiveIcon, + Modal, NumericalCell, PencilIcon, TableRow, @@ -32,7 +34,6 @@ import { import * as React from 'react'; import { deleteCondition } from '../../../api/quality-gates'; import withMetricsContext from '../../../app/components/metrics/withMetricsContext'; -import ConfirmModal from '../../../components/controls/ConfirmModal'; import { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n'; import { CaycStatus, @@ -187,19 +188,24 @@ export class ConditionComponent extends React.PureComponent<Props, State> { size="small" /> {this.state.deleteFormOpen && ( - <ConfirmModal - confirmButtonText={translate('delete')} - confirmData={condition} - header={translate('quality_gates.delete_condition')} - isDestructive + <Modal + headerTitle={translate('quality_gates.delete_condition')} onClose={this.closeDeleteForm} - onConfirm={this.removeCondition} - > - {translateWithParameters( + body={translateWithParameters( 'quality_gates.delete_condition.confirm.message', getLocalizedMetricName(this.props.metric), )} - </ConfirmModal> + primaryButton={ + <DangerButtonPrimary + autoFocus + type="submit" + onClick={() => this.removeCondition(condition)} + > + {translate('delete')} + </DangerButtonPrimary> + } + secondaryButtonLabel={translate('close')} + /> )} </> )} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx index 5865e74e560..e2bfb237257 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx @@ -17,11 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { ButtonPrimary, FlagMessage, FormField, Modal, RadioButton } from 'design-system'; import * as React from 'react'; import { createCondition, updateCondition } from '../../../api/quality-gates'; -import ConfirmModal from '../../../components/controls/ConfirmModal'; -import Radio from '../../../components/controls/Radio'; -import { Alert } from '../../../components/ui/Alert'; import { getLocalizedMetricName, translate } from '../../../helpers/l10n'; import { isDiffMetric } from '../../../helpers/measures'; import { Condition, Metric, QualityGate } from '../../../types/types'; @@ -48,6 +46,8 @@ interface State { scope: 'new' | 'overall'; } +const ADD_CONDITION_MODAL_ID = 'add-condition-modal'; + export default class ConditionModal extends React.PureComponent<Props, State> { constructor(props: Props) { super(props); @@ -64,7 +64,9 @@ export default class ConditionModal extends React.PureComponent<Props, State> { return Array.isArray(operators) ? undefined : operators; } - handleFormSubmit = () => { + handleFormSubmit = (event: React.FormEvent<HTMLFormElement>) => { + event.preventDefault(); + const { condition, qualityGate } = this.props; const newCondition: Omit<Condition, 'id'> = { metric: this.state.metric!.key, @@ -74,7 +76,7 @@ export default class ConditionModal extends React.PureComponent<Props, State> { const submitPromise = condition ? updateCondition({ id: condition.id, ...newCondition }) : createCondition({ gateName: qualityGate.name, ...newCondition }); - return submitPromise.then(this.props.onAddCondition); + return submitPromise.then(this.props.onAddCondition).then(this.props.onClose); }; handleScopeChange = (scope: 'new' | 'overall') => { @@ -104,44 +106,43 @@ export default class ConditionModal extends React.PureComponent<Props, State> { this.setState({ error }); }; - render() { - const { header, metrics, onClose } = this.props; - const { op, error, scope, metric } = this.state; - return ( - <ConfirmModal - confirmButtonText={header} - confirmDisable={metric === undefined} - header={header} - onClose={onClose} - onConfirm={this.handleFormSubmit} - size="small" - > - {this.state.errorMessage && <Alert variant="error">{this.state.errorMessage}</Alert>} + renderBody = () => { + const { metrics } = this.props; + const { op, error, scope, metric, errorMessage } = this.state; + return ( + <form id={ADD_CONDITION_MODAL_ID} onSubmit={this.handleFormSubmit}> + {errorMessage && ( + <FlagMessage className="sw-mb-2" variant="error"> + {errorMessage} + </FlagMessage> + )} {this.props.metric === undefined && ( - <div className="modal-field display-flex-center"> - <Radio checked={scope === 'new'} onCheck={this.handleScopeChange} value="new"> - <span data-test="quality-gates__condition-scope-new"> - {translate('quality_gates.conditions.new_code')} - </span> - </Radio> - <Radio - checked={scope === 'overall'} - className="big-spacer-left" - onCheck={this.handleScopeChange} - value="overall" - > - <span data-test="quality-gates__condition-scope-overall"> - {translate('quality_gates.conditions.overall_code')} - </span> - </Radio> - </div> + <FormField label={translate('quality_gates.conditions.where')}> + <div className="sw-flex sw-gap-4"> + <RadioButton checked={scope === 'new'} onCheck={this.handleScopeChange} value="new"> + <span data-test="quality-gates__condition-scope-new"> + {translate('quality_gates.conditions.new_code')} + </span> + </RadioButton> + <RadioButton + checked={scope === 'overall'} + onCheck={this.handleScopeChange} + value="overall" + > + <span data-test="quality-gates__condition-scope-overall"> + {translate('quality_gates.conditions.overall_code')} + </span> + </RadioButton> + </div> + </FormField> )} - <div className="modal-field"> - <label htmlFor="condition-metric"> - {translate('quality_gates.conditions.fails_when')} - </label> + <FormField + description={this.props.metric && getLocalizedMetricName(this.props.metric)} + htmlFor="condition-metric" + label={translate('quality_gates.conditions.fails_when')} + > {metrics && ( <MetricSelect metric={metric} @@ -151,37 +152,61 @@ export default class ConditionModal extends React.PureComponent<Props, State> { onMetricChange={this.handleMetricChange} /> )} - {this.props.metric && ( - <span className="note">{getLocalizedMetricName(this.props.metric)}</span> - )} - </div> + </FormField> {metric && ( - <> - <div className="modal-field display-inline-block"> - <label id="condition-operator-label"> - {translate('quality_gates.conditions.operator')} - </label> + <div className="sw-flex sw-gap-2"> + <FormField + className="sw-mb-0" + htmlFor="condition-operator" + label={translate('quality_gates.conditions.operator')} + > <ConditionOperator metric={metric} onOperatorChange={this.handleOperatorChange} op={op} /> - </div> - <div className="modal-field display-inline-block spacer-left"> - <label id="condition-threshold-label"> - {translate('quality_gates.conditions.value')} - </label> + </FormField> + <FormField + htmlFor="condition-threshold" + label={translate('quality_gates.conditions.value')} + > <ThresholdInput metric={metric} name="error" onChange={this.handleErrorChange} value={error} /> - </div> - </> + </FormField> + </div> )} - </ConfirmModal> + </form> + ); + }; + + render() { + const { header } = this.props; + const { metric } = this.state; + return ( + <Modal + isScrollable={false} + isOverflowVisible + headerTitle={header} + onClose={this.props.onClose} + body={this.renderBody()} + primaryButton={ + <ButtonPrimary + autoFocus + disabled={metric === undefined} + id="add-condition-button" + form={ADD_CONDITION_MODAL_ID} + type="submit" + > + {header} + </ButtonPrimary> + } + secondaryButtonLabel={translate('close')} + /> ); } } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionOperator.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionOperator.tsx index e4153aa49aa..102373a259e 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionOperator.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionOperator.tsx @@ -17,8 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +import { InputSelect, Note } from 'design-system'; import * as React from 'react'; -import Select from '../../../components/controls/Select'; import { translate } from '../../../helpers/l10n'; import { Metric } from '../../../types/types'; import { getPossibleOperators } from '../utils'; @@ -50,12 +51,11 @@ export default class ConditionOperator extends React.PureComponent<Props> { }); return ( - <Select + <InputSelect autoFocus - aria-labelledby="condition-operator-label" - className="input-medium" + size="small" isClearable={false} - id="condition-operator" + inputId="condition-operator" name="operator" onChange={this.handleChange} options={operatorOptions} @@ -63,12 +63,8 @@ export default class ConditionOperator extends React.PureComponent<Props> { value={operatorOptions.filter((o) => o.value === this.props.op)} /> ); - } else { - return ( - <span className="display-inline-block note abs-width-150"> - {this.getLabel(operators, this.props.metric)} - </span> - ); } + + return <Note className="sw-w-abs-150">{this.getLabel(operators, this.props.metric)}</Note>; } } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionReviewAndUpdateModal.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionReviewAndUpdateModal.tsx index ed21ba444d9..93352875881 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionReviewAndUpdateModal.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionReviewAndUpdateModal.tsx @@ -17,12 +17,12 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { ButtonPrimary, Link, Modal, SubHeading, Title } from 'design-system'; import { sortBy } from 'lodash'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { createCondition, updateCondition } from '../../../api/quality-gates'; -import DocLink from '../../../components/common/DocLink'; -import ConfirmModal from '../../../components/controls/ConfirmModal'; +import { useDocUrl } from '../../../helpers/docs'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { Condition, Dict, Metric, QualityGate } from '../../../types/types'; import { getCorrectCaycCondition, getWeakMissingAndNonCaycConditions } from '../utils'; @@ -42,9 +42,31 @@ interface Props { qualityGate: QualityGate; } -export default class CaycReviewUpdateConditionsModal extends React.PureComponent<Props> { - updateCaycQualityGate = () => { - const { conditions, qualityGate } = this.props; +export default function CaycReviewUpdateConditionsModal(props: Props) { + const { + conditions, + qualityGate, + metrics, + onSaveCondition, + onAddCondition, + lockEditing, + onClose, + } = props; + + const { weakConditions, missingConditions } = getWeakMissingAndNonCaycConditions(conditions); + const sortedWeakConditions = sortBy( + weakConditions, + (condition) => metrics[condition.metric] && metrics[condition.metric].name + ); + + const sortedMissingConditions = sortBy( + missingConditions, + (condition) => metrics[condition.metric] && metrics[condition.metric].name + ); + + const getDocUrl = useDocUrl(); + + const updateCaycQualityGate = React.useCallback(() => { const promiseArr: Promise<Condition | undefined | void>[] = []; const { weakConditions, missingConditions } = getWeakMissingAndNonCaycConditions(conditions); @@ -57,10 +79,10 @@ export default class CaycReviewUpdateConditionsModal extends React.PureComponent .then((resultCondition) => { const currentCondition = conditions.find((con) => con.metric === condition.metric); if (currentCondition) { - this.props.onSaveCondition(resultCondition, currentCondition); + onSaveCondition(resultCondition, currentCondition); } }) - .catch(() => undefined), + .catch(() => undefined) ); }); @@ -70,95 +92,92 @@ export default class CaycReviewUpdateConditionsModal extends React.PureComponent ...getCorrectCaycCondition(condition), gateName: qualityGate.name, }) - .then((resultCondition) => this.props.onAddCondition(resultCondition)) - .catch(() => undefined), + .then((resultCondition) => onAddCondition(resultCondition)) + .catch(() => undefined) ); }); return Promise.all(promiseArr).then(() => { - this.props.lockEditing(); + lockEditing(); }); - }; - - render() { - const { conditions, qualityGate, metrics } = this.props; - - const { weakConditions, missingConditions } = getWeakMissingAndNonCaycConditions(conditions); - const sortedWeakConditions = sortBy( - weakConditions, - (condition) => metrics[condition.metric]?.name, - ); + }, [conditions, qualityGate, lockEditing, onAddCondition, onSaveCondition]); - const sortedMissingConditions = sortBy( - missingConditions, - (condition) => metrics[condition.metric]?.name, - ); + const body = ( + <div className="sw-mb-10"> + <SubHeading as="p" className="sw-body-sm"> + <FormattedMessage + id="quality_gates.cayc.review_update_modal.description1" + defaultMessage={translate('quality_gates.cayc.review_update_modal.description1')} + values={{ + cayc_link: ( + <Link to={getDocUrl('/user-guide/clean-as-you-code/')}> + {translate('quality_gates.cayc')} + </Link> + ), + }} + /> + </SubHeading> - return ( - <ConfirmModal - header={translateWithParameters( - 'quality_gates.cayc.review_update_modal.header', - qualityGate.name, - )} - confirmButtonText={translate('quality_gates.cayc.review_update_modal.confirm_text')} - onClose={this.props.onClose} - onConfirm={this.updateCaycQualityGate} - size="medium" - > - <div className="quality-gate-section huge-spacer-bottom"> - <p> - <FormattedMessage - id="quality_gates.cayc.review_update_modal.description1" - defaultMessage={translate('quality_gates.cayc.review_update_modal.description1')} - values={{ - cayc_link: ( - <DocLink to="/user-guide/clean-as-you-code/"> - {translate('quality_gates.cayc')} - </DocLink> - ), - }} - /> - </p> + {sortedMissingConditions.length > 0 && ( + <> + <Title as="h4" className="sw-mb-2 sw-mt-4 sw-body-sm-highlight"> + {translateWithParameters( + 'quality_gates.cayc.review_update_modal.add_condition.header', + sortedMissingConditions.length + )} + </Title> + <ConditionsTable + {...props} + conditions={sortedMissingConditions} + showEdit={false} + isCaycModal + /> + </> + )} - {sortedMissingConditions.length > 0 && ( - <> - <h4 className="big-spacer-top spacer-bottom"> - {translateWithParameters( - 'quality_gates.cayc.review_update_modal.add_condition.header', - sortedMissingConditions.length, - )} - </h4> - <ConditionsTable - {...this.props} - conditions={sortedMissingConditions} - showEdit={false} - isCaycModal - /> - </> - )} + {sortedWeakConditions.length > 0 && ( + <> + <Title as="h4" className="sw-mb-2 sw-mt-4 sw-body-sm-highlight"> + {translateWithParameters( + 'quality_gates.cayc.review_update_modal.modify_condition.header', + sortedWeakConditions.length + )} + </Title> + <ConditionsTable + {...props} + conditions={sortedWeakConditions} + showEdit={false} + isCaycModal + /> + </> + )} - {sortedWeakConditions.length > 0 && ( - <> - <h4 className="big-spacer-top spacer-bottom"> - {translateWithParameters( - 'quality_gates.cayc.review_update_modal.modify_condition.header', - sortedWeakConditions.length, - )} - </h4> - <ConditionsTable - {...this.props} - conditions={sortedWeakConditions} - showEdit={false} - isCaycModal - /> - </> - )} + <Title as="h4" className="sw-mb-2 sw-mt-4 sw-body-sm-highlight"> + {translate('quality_gates.cayc.review_update_modal.description2')} + </Title> + </div> + ); - <h4 className="big-spacer-top spacer-bottom"> - {translate('quality_gates.cayc.review_update_modal.description2')} - </h4> - </div> - </ConfirmModal> - ); - } + return ( + <Modal + isLarge + headerTitle={translateWithParameters( + 'quality_gates.cayc.review_update_modal.header', + qualityGate.name + )} + onClose={onClose} + body={body} + primaryButton={ + <ButtonPrimary + autoFocus + id="fix-quality-gate" + type="submit" + onClick={updateCaycQualityGate} + > + {translate('quality_gates.cayc.review_update_modal.confirm_text')} + </ButtonPrimary> + } + secondaryButtonLabel={translate('close')} + /> + ); } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx index 4ec8188e8d7..271bbef4a75 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx @@ -17,10 +17,11 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { LabelValueSelectOption, SearchSelectDropdown } from 'design-system'; import { sortBy } from 'lodash'; import * as React from 'react'; +import { Options } from 'react-select'; import withMetricsContext from '../../../app/components/metrics/withMetricsContext'; -import Select from '../../../components/controls/Select'; import { getLocalizedMetricDomain, translate } from '../../../helpers/l10n'; import { Dict, Metric } from '../../../types/types'; import { getLocalizedMetricNameNoDiffMetric } from '../utils'; @@ -38,54 +39,62 @@ interface Option { value: string; } -export class MetricSelect extends React.PureComponent<Props> { - handleChange = (option: Option | null) => { +export function MetricSelect({ metric, metricsArray, metrics, onMetricChange }: Props) { + const handleChange = (option: Option | null) => { if (option) { - const { metricsArray: metrics } = this.props; - const selectedMetric = metrics.find((metric) => metric.key === option.value); + const selectedMetric = metricsArray.find((metric) => metric.key === option.value); if (selectedMetric) { - this.props.onMetricChange(selectedMetric); + onMetricChange(selectedMetric); } } }; - render() { - const { metric, metricsArray, metrics } = this.props; + const options: Array<Option & { domain?: string }> = sortBy( + metricsArray.map((m) => ({ + value: m.key, + label: getLocalizedMetricNameNoDiffMetric(m, metrics), + domain: m.domain, + })), + 'domain' + ); - const options: Array<Option & { domain?: string }> = sortBy( - metricsArray.map((m) => ({ - value: m.key, - label: getLocalizedMetricNameNoDiffMetric(m, metrics), - domain: m.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: '<domain>', + label: getLocalizedMetricDomain(option.domain), + isDisabled: true, + }); + } + optionsWithDomains.push(option); + }); - // 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: '<domain>', - label: getLocalizedMetricDomain(option.domain), - isDisabled: true, - }); - } - optionsWithDomains.push(option); - }); + const handleAssigneeSearch = React.useCallback( + (query: string, resolve: (options: Options<LabelValueSelectOption<string>>) => void) => { + resolve(options.filter((opt) => opt.label.toLowerCase().includes(query.toLowerCase()))); + }, + [options] + ); - return ( - <Select - className="text-middle quality-gate-metric-select" - id="condition-metric" - onChange={this.handleChange} - options={optionsWithDomains} - placeholder={translate('search.search_for_metrics')} - value={optionsWithDomains.find((o) => o.value === metric?.key)} - /> - ); - } + return ( + <SearchSelectDropdown + aria-label={translate('search.search_for_metrics')} + size="large" + controlSize="full" + inputId="condition-metric" + isClearable + defaultOptions={optionsWithDomains} + loadOptions={handleAssigneeSearch} + onChange={handleChange} + placeholder={translate('search.search_for_metrics')} + controlLabel={ + optionsWithDomains.find((o) => o.value === metric?.key)?.label ?? translate('select_verb') + } + /> + ); } export default withMetricsContext(MetricSelect); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ThresholdInput.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ThresholdInput.tsx index 9657fb14592..770468e5e45 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/ThresholdInput.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ThresholdInput.tsx @@ -17,8 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { InputField, InputSelect } from 'design-system'; import * as React from 'react'; -import Select from '../../../components/controls/Select'; +import { LabelValueSelectOption } from '../../../components/controls/Select'; import { Metric } from '../../../types/types'; interface Props { @@ -33,8 +34,12 @@ export default class ThresholdInput extends React.PureComponent<Props> { this.props.onChange(e.currentTarget.value); }; - handleSelectChange = (option: { value: string }) => { - this.props.onChange(option.value); + handleSelectChange = (option: LabelValueSelectOption) => { + if (option) { + this.props.onChange(option.value); + } else { + this.props.onChange(''); + } }; renderRatingInput() { @@ -48,16 +53,14 @@ export default class ThresholdInput extends React.PureComponent<Props> { ]; return ( - <Select - className="input-tiny text-middle" - aria-labelledby="condition-threshold-label" - isClearable={false} - id="condition-threshold" + <InputSelect + className="sw-w-abs-150" + inputId="condition-threshold" name={name} onChange={this.handleSelectChange} options={options} placeholder="" - isSearchable={false} + size="small" value={options.find((o) => o.value === value)} /> ); @@ -71,9 +74,8 @@ export default class ThresholdInput extends React.PureComponent<Props> { } return ( - <input - className="input-tiny text-middle" - aria-labelledby="condition-threshold-label" + <InputField + size="small" data-type={metric.type} id="condition-threshold" name={name} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx index 67560e0cfe0..de602a125b0 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx @@ -193,8 +193,8 @@ it('should be able to add a condition', async () => { await user.click(dialog.getByRole('radio', { name: 'quality_gates.conditions.new_code' })); await selectEvent.select(dialog.getByRole('combobox'), ['Issues']); await user.click(dialog.getByRole('textbox', { name: 'quality_gates.conditions.value' })); - await user.keyboard('12{Enter}'); - + await user.keyboard('12'); + await user.click(dialog.getByRole('button', { name: 'quality_gates.add_condition' })); const newConditions = within(await screen.findByTestId('quality-gates__conditions-new')); expect(await newConditions.findByRole('cell', { name: 'Issues' })).toBeInTheDocument(); expect(await newConditions.findByRole('cell', { name: '12' })).toBeInTheDocument(); @@ -209,7 +209,8 @@ it('should be able to add a condition', async () => { await user.click(dialog.getByText('quality_gates.operator.LT')); await user.click(dialog.getByRole('textbox', { name: 'quality_gates.conditions.value' })); - await user.keyboard('42{Enter}'); + await user.keyboard('42'); + await user.click(dialog.getByRole('button', { name: 'quality_gates.add_condition' })); const overallConditions = within(await screen.findByTestId('quality-gates__conditions-overall')); diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index a38463d3719..a92d413d8ce 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -2146,6 +2146,7 @@ quality_gates.conditions.operator=Operator quality_gates.conditions.warning=Warning quality_gates.conditions.warning.tooltip=Warning status is deprecated and will disappear with the next update of the Quality Gate. quality_gates.conditions.value=Value +quality_gates.conditions.where=Where? quality_gates.duplicated_conditions=This quality gate has duplicated conditions: quality_gates.intro.1=Quality Gate is the set of conditions the project must meet before it can be released into production. quality_gates.intro.2=It is possible to set a default Quality Gate, which will be applied to all projects not explicitly assigned to some other gate. |