diff options
author | Stas Vilchik <stas.vilchik@sonarsource.com> | 2018-02-21 16:36:42 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-02-21 16:36:42 +0100 |
commit | e353cd6f2bd37ea143d5806e9ee85323cddd1f97 (patch) | |
tree | baed6993aa339baa41e32e653b457a9f66ee1a24 /server | |
parent | a90637886c930df4d6da31e48e72b90707374730 (diff) | |
download | sonarqube-e353cd6f2bd37ea143d5806e9ee85323cddd1f97.tar.gz sonarqube-e353cd6f2bd37ea143d5806e9ee85323cddd1f97.zip |
create and use Button component (#3087)
Diffstat (limited to 'server')
166 files changed, 1347 insertions, 1425 deletions
diff --git a/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/LoginPage.java b/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/LoginPage.java index a11730125aa..3f765429d47 100644 --- a/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/LoginPage.java +++ b/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/LoginPage.java @@ -22,7 +22,6 @@ package org.sonarqube.qa.util.pageobjects; import com.codeborne.selenide.Condition; import com.codeborne.selenide.Selenide; import com.codeborne.selenide.SelenideElement; -import org.openqa.selenium.By; public class LoginPage { @@ -49,7 +48,7 @@ public class LoginPage { public LoginPage submitWrongCredentials(String login, String password) { Selenide.$("#login").val(login); Selenide.$("#password").val(password); - Selenide.$(By.name("commit")).click(); + Selenide.$("[type=submit]").click(); return Selenide.page(LoginPage.class); } @@ -60,7 +59,7 @@ public class LoginPage { private static <T> T submitCredentials(String login, String password, Class<T> expectedResultPage) { Selenide.$("#login").val(login); Selenide.$("#password").val(password); - Selenide.$(By.name("commit")).click(); + Selenide.$("[type=submit]").click(); Selenide.$("#login").should(Condition.disappear); return Selenide.page(expectedResultPage); } diff --git a/server/sonar-web/src/main/js/api/quality-profiles.ts b/server/sonar-web/src/main/js/api/quality-profiles.ts index 736de049edb..e589638ac76 100644 --- a/server/sonar-web/src/main/js/api/quality-profiles.ts +++ b/server/sonar-web/src/main/js/api/quality-profiles.ts @@ -112,7 +112,7 @@ export function getProfileProjects(data: RequestData): Promise<any> { } export function getProfileInheritance(profileKey: string): Promise<any> { - return getJSON('/api/qualityprofiles/inheritance', { profileKey }); + return getJSON('/api/qualityprofiles/inheritance', { profileKey }).catch(throwGlobalError); } export function setDefaultProfile(profileKey: string): Promise<void> { @@ -135,8 +135,10 @@ export function changeProfileParent(profileKey: string, parentKey: string): Prom return post('/api/qualityprofiles/change_parent', { profileKey, parentKey }); } -export function getImporters(): Promise<any> { - return getJSON('/api/qualityprofiles/importers').then(r => r.importers); +export function getImporters(): Promise< + Array<{ key: string; languages: Array<string>; name: string }> +> { + return getJSON('/api/qualityprofiles/importers').then(r => r.importers, throwGlobalError); } export function getExporters(): Promise<any> { diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsEditionsNotif.tsx b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsEditionsNotif.tsx index e5a5d8dfef4..3c8850a95ef 100644 --- a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsEditionsNotif.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsEditionsNotif.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import NavBarNotif from '../../../../components/nav/NavBarNotif'; import RestartForm from '../../../../components/common/RestartForm'; +import { Button } from '../../../../components/ui/buttons'; import { dismissErrorMessage, Edition, EditionStatus } from '../../../../api/marketplace'; import { translate, translateWithParameters } from '../../../../helpers/l10n'; @@ -86,6 +87,7 @@ export default class SettingsEditionsNotif extends React.PureComponent<Props, St url: ( <a href="https://redirect.sonarsource.com/doc/data-center-edition.html" + rel="noopener noreferrer" target="_blank"> {edition.name} </a> @@ -95,9 +97,9 @@ export default class SettingsEditionsNotif extends React.PureComponent<Props, St </span> )} {!preventRestart && ( - <button className="js-restart spacer-left" onClick={this.handleOpenRestart} type="button"> + <Button className="js-restart spacer-left" onClick={this.handleOpenRestart}> {translate('marketplace.restart')} - </button> + </Button> )} {!preventRestart && this.state.openRestart && <RestartForm onClose={this.hanleCloseRestart} />} diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsEditionsNotif-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsEditionsNotif-test.tsx.snap index 1b1380978bf..f843a2d29ba 100644 --- a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsEditionsNotif-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsEditionsNotif-test.tsx.snap @@ -30,13 +30,12 @@ exports[`should display a ready notification 1`] = ` <span> marketplace.edition_status.AUTOMATIC_READY </span> - <button + <Button className="js-restart spacer-left" onClick={[Function]} - type="button" > marketplace.restart - </button> + </Button> </NavBarNotif> `; diff --git a/server/sonar-web/src/main/js/apps/account/organizations/CreateOrganizationForm.tsx b/server/sonar-web/src/main/js/apps/account/organizations/CreateOrganizationForm.tsx index 53117d71a12..04254a29e1a 100644 --- a/server/sonar-web/src/main/js/apps/account/organizations/CreateOrganizationForm.tsx +++ b/server/sonar-web/src/main/js/apps/account/organizations/CreateOrganizationForm.tsx @@ -25,6 +25,7 @@ import { createOrganization } from '../../organizations/actions'; import { Organization } from '../../../app/types'; import Modal from '../../../components/controls/Modal'; import { translate } from '../../../helpers/l10n'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; interface DispatchProps { createOrganization: (fields: Partial<Organization>) => Promise<{ key: string }>; @@ -136,16 +137,16 @@ class CreateOrganizationForm extends React.PureComponent<Props, State> { <em className="mandatory">*</em> </label> <input - id="organization-name" autoFocus={true} + disabled={this.state.loading} + id="organization-name" + maxLength={64} + minLength={2} name="name" + onChange={this.handleNameChange} required={true} type="text" - minLength={2} - maxLength={64} value={this.state.name} - disabled={this.state.loading} - onChange={this.handleNameChange} /> <div className="modal-field-description"> {translate('organization.name.description')} @@ -154,14 +155,14 @@ class CreateOrganizationForm extends React.PureComponent<Props, State> { <div className="modal-field"> <label htmlFor="organization-key">{translate('organization.key')}</label> <input + disabled={this.state.loading} id="organization-key" + maxLength={64} + minLength={2} name="key" + onChange={this.handleKeyChange} type="text" - minLength={2} - maxLength={64} value={this.state.key} - disabled={this.state.loading} - onChange={this.handleKeyChange} /> <div className="modal-field-description"> {translate('organization.key.description')} @@ -170,13 +171,13 @@ class CreateOrganizationForm extends React.PureComponent<Props, State> { <div className="modal-field"> <label htmlFor="organization-avatar">{translate('organization.avatar')}</label> <input + disabled={this.state.loading} id="organization-avatar" + maxLength={256} name="avatar" + onChange={this.handleAvatarInputChange} type="text" - maxLength={256} value={this.state.avatar} - disabled={this.state.loading} - onChange={this.handleAvatarInputChange} /> <div className="modal-field-description"> {translate('organization.avatar.description')} @@ -187,20 +188,20 @@ class CreateOrganizationForm extends React.PureComponent<Props, State> { {translate('organization.avatar.preview')} {':'} </div> - <img src={this.state.avatarImage} alt="" height={30} /> + <img alt="" height={30} src={this.state.avatarImage} /> </div> )} </div> <div className="modal-field"> <label htmlFor="organization-description">{translate('description')}</label> <textarea + disabled={this.state.loading} id="organization-description" + maxLength={256} name="description" + onChange={this.handleDescriptionChange} rows={3} - maxLength={256} value={this.state.description} - disabled={this.state.loading} - onChange={this.handleDescriptionChange} /> <div className="modal-field-description"> {translate('organization.description.description')} @@ -209,13 +210,13 @@ class CreateOrganizationForm extends React.PureComponent<Props, State> { <div className="modal-field"> <label htmlFor="organization-url">{translate('organization.url')}</label> <input + disabled={this.state.loading} id="organization-url" + maxLength={256} name="url" + onChange={this.handleUrlChange} type="text" - maxLength={256} value={this.state.url} - disabled={this.state.loading} - onChange={this.handleUrlChange} /> <div className="modal-field-description"> {translate('organization.url.description')} @@ -226,12 +227,8 @@ class CreateOrganizationForm extends React.PureComponent<Props, State> { <footer className="modal-foot"> <div> {this.state.loading && <i className="spinner spacer-right" />} - <button disabled={this.state.loading} type="submit"> - {translate('create')} - </button> - <button className="button-link" onClick={this.props.onClose} type="reset"> - {translate('cancel')} - </button> + <SubmitButton disabled={this.state.loading}>{translate('create')}</SubmitButton> + <ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink> </div> </footer> </form> diff --git a/server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.tsx b/server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.tsx index 6ec85e94b43..9bf4071c8b0 100644 --- a/server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.tsx +++ b/server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.tsx @@ -26,6 +26,7 @@ import { fetchIfAnyoneCanCreateOrganizations } from './actions'; import { translate } from '../../../helpers/l10n'; import { getAppState, getMyOrganizations, getGlobalSettingValue } from '../../../store/rootReducer'; import { Organization } from '../../../app/types'; +import { Button } from '../../../components/ui/buttons'; interface StateProps { anyoneCanCreate?: { value: string }; @@ -63,18 +64,12 @@ class UserOrganizations extends React.PureComponent<Props, State> { } }; - openCreateOrganizationForm = () => this.setState({ createOrganization: true }); - - closeCreateOrganizationForm = () => this.setState({ createOrganization: false }); - - handleCreateClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); - this.openCreateOrganizationForm(); + openCreateOrganizationForm = () => { + this.setState({ createOrganization: true }); }; - handleCreate = () => { - this.closeCreateOrganizationForm(); + closeCreateOrganizationForm = () => { + this.setState({ createOrganization: false }); }; render() { @@ -91,7 +86,7 @@ class UserOrganizations extends React.PureComponent<Props, State> { {canCreateOrganizations && ( <div className="clearfix"> <div className="boxed-group-actions"> - <button onClick={this.handleCreateClick}>{translate('create')}</button> + <Button onClick={this.openCreateOrganizationForm}>{translate('create')}</Button> </div> </div> )} @@ -107,7 +102,7 @@ class UserOrganizations extends React.PureComponent<Props, State> { {this.state.createOrganization && ( <CreateOrganizationForm onClose={this.closeCreateOrganizationForm} - onCreate={this.handleCreate} + onCreate={this.closeCreateOrganizationForm} /> )} </div> diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/WorkersForm.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/WorkersForm.tsx index 91c5b7d3747..52d5eee1094 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/WorkersForm.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/WorkersForm.tsx @@ -22,6 +22,7 @@ import { times } from 'lodash'; import { setWorkerCount } from '../../../api/ce'; import Modal from '../../../components/controls/Modal'; import Select from '../../../components/controls/Select'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; const MAX_WORKERS = 10; @@ -55,7 +56,9 @@ export default class WorkersForm extends React.PureComponent<Props, State> { this.mounted = false; } - handleClose = () => this.props.onClose(); + handleClose = () => { + this.props.onClose(); + }; handleWorkerCountChange = (option: { value: number }) => this.setState({ newWorkerCount: option.value }); @@ -105,12 +108,8 @@ export default class WorkersForm extends React.PureComponent<Props, State> { <footer className="modal-foot"> <div> {this.state.submitting && <i className="spinner spacer-right" />} - <button disabled={this.state.submitting} type="submit"> - {translate('save')} - </button> - <button type="reset" className="button-link" onClick={this.handleClose}> - {translate('cancel')} - </button> + <SubmitButton disabled={this.state.submitting}>{translate('save')}</SubmitButton> + <ResetButtonLink onClick={this.handleClose}>{translate('cancel')}</ResetButtonLink> </div> </footer> </form> diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/WorkersForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/WorkersForm-test.tsx.snap index 4b9dfd60686..0b1320c6cb2 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/WorkersForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/WorkersForm-test.tsx.snap @@ -79,19 +79,16 @@ exports[`changes select 1`] = ` className="modal-foot" > <div> - <button + <SubmitButton disabled={false} - type="submit" > save - </button> - <button - className="button-link" + </SubmitButton> + <ResetButtonLink onClick={[Function]} - type="reset" > cancel - </button> + </ResetButtonLink> </div> </footer> </form> @@ -177,19 +174,16 @@ exports[`changes select 2`] = ` className="modal-foot" > <div> - <button + <SubmitButton disabled={false} - type="submit" > save - </button> - <button - className="button-link" + </SubmitButton> + <ResetButtonLink onClick={[Function]} - type="reset" > cancel - </button> + </ResetButtonLink> </div> </footer> </form> diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationButton.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationButton.tsx index 97ae4439f34..043d71b134a 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationButton.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationButton.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import ActivationFormModal from './ActivationFormModal'; import { Profile as BaseProfile } from '../../../api/quality-profiles'; import { Rule, RuleDetails, RuleActivation } from '../../../app/types'; +import { Button } from '../../../components/ui/buttons'; interface Props { activation?: RuleActivation; @@ -50,23 +51,23 @@ export default class ActivationButton extends React.PureComponent<Props, State> this.mounted = false; } - handleButtonClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); + handleButtonClick = () => { this.setState({ modal: true }); }; - handleCloseModal = () => this.setState({ modal: false }); + handleCloseModal = () => { + this.setState({ modal: false }); + }; render() { return ( <> - <button + <Button className={this.props.className} id="coding-rules-quality-profile-activate" onClick={this.handleButtonClick}> {this.props.buttonText} - </button> + </Button> {this.state.modal && ( <ActivationFormModal diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx index c410a105cae..97fa22a0402 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx @@ -27,6 +27,7 @@ import { Rule, RuleDetails, RuleActivation } from '../../../app/types'; import { SEVERITIES } from '../../../helpers/constants'; import { translate } from '../../../helpers/l10n'; import { sortProfiles } from '../../quality-profiles/utils'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; interface Props { activation?: RuleActivation; @@ -84,8 +85,8 @@ export default class ActivationFormModal extends React.PureComponent<Props, Stat }; // Choose QP which a user can administrate, which are the same language and which are not built-in - getQualityProfilesWithDepth = ({ profiles } = this.props) => - sortProfiles( + getQualityProfilesWithDepth = ({ profiles } = this.props) => { + return sortProfiles( profiles.filter( profile => !profile.isBuiltIn && @@ -98,11 +99,6 @@ export default class ActivationFormModal extends React.PureComponent<Props, Stat // Decrease depth by 1, so the top level starts at 0 depth: profile.depth - 1 })); - - handleCancelClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); - this.props.onClose(); }; handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { @@ -137,11 +133,17 @@ export default class ActivationFormModal extends React.PureComponent<Props, Stat this.setState((state: State) => ({ params: { ...state.params, [name]: value } })); }; - handleProfileChange = ({ value }: { value: string }) => this.setState({ profile: value }); + handleProfileChange = ({ value }: { value: string }) => { + this.setState({ profile: value }); + }; - handleSeverityChange = ({ value }: { value: string }) => this.setState({ severity: value }); + handleSeverityChange = ({ value }: { value: string }) => { + this.setState({ severity: value }); + }; - renderSeverityOption = ({ value }: { value: string }) => <SeverityHelper severity={value} />; + renderSeverityOption = ({ value }: { value: string }) => { + return <SeverityHelper severity={value} />; + }; render() { const { activation, rule } = this.props; @@ -188,11 +190,11 @@ export default class ActivationFormModal extends React.PureComponent<Props, Stat clearable={false} disabled={submitting} onChange={this.handleSeverityChange} + optionRenderer={this.renderSeverityOption} options={SEVERITIES.map(severity => ({ label: translate('severity', severity), value: severity }))} - optionRenderer={this.renderSeverityOption} searchable={false} value={severity} valueRenderer={this.renderSeverityOption} @@ -241,16 +243,12 @@ export default class ActivationFormModal extends React.PureComponent<Props, Stat <footer className="modal-foot"> {submitting && <i className="spinner spacer-right" />} - <button disabled={submitting || activeInAllProfiles} type="submit"> + <SubmitButton disabled={submitting || activeInAllProfiles}> {isUpdateMode ? translate('save') : translate('coding_rules.activate')} - </button> - <button - className="button-link" - disabled={submitting} - onClick={this.handleCancelClick} - type="reset"> + </SubmitButton> + <ResetButtonLink disabled={submitting} onClick={this.props.onClose}> {translate('cancel')} - </button> + </ResetButtonLink> </footer> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChange.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChange.tsx index 5b4d9e3c665..63d039166cc 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChange.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChange.tsx @@ -23,6 +23,7 @@ import BulkChangeModal from './BulkChangeModal'; import { Query } from '../query'; import { Profile } from '../../../api/quality-profiles'; import Dropdown from '../../../components/controls/Dropdown'; +import { Button } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; interface Props { @@ -109,9 +110,9 @@ export default class BulkChange extends React.PureComponent<Props, State> { this.closeDropdown = closeDropdown; return ( <div className={classNames('pull-left dropdown', { open })}> - <button className="js-bulk-change" onClick={onToggleClick}> + <Button className="js-bulk-change" onClick={onToggleClick}> {translate('bulk_change')} - </button> + </Button> <ul className="dropdown-menu"> <li> <a href="#" onClick={this.handleActivateClick}> diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx index f7981285692..948d3743693 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx @@ -25,6 +25,7 @@ import Modal from '../../../components/controls/Modal'; import Select from '../../../components/controls/Select'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { formatMeasure } from '../../../helpers/measures'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; interface Props { action: string; @@ -73,12 +74,6 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> { this.mounted = false; } - handleCloseClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); - this.props.onClose(); - }; - handleProfileSelect = (options: { value: string }[]) => { const selectedProfiles = options.map(option => option.value); this.setState({ selectedProfiles }); @@ -238,16 +233,13 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> { <footer className="modal-foot"> {this.state.submitting && <i className="spinner spacer-right" />} {!this.state.finished && ( - <button - disabled={this.state.submitting} - id="coding-rules-submit-bulk-change" - type="submit"> + <SubmitButton disabled={this.state.submitting} id="coding-rules-submit-bulk-change"> {translate('apply')} - </button> + </SubmitButton> )} - <button className="button-link" onClick={this.handleCloseClick} type="reset"> + <ResetButtonLink onClick={this.props.onClose}> {this.state.finished ? translate('close') : translate('cancel')} - </button> + </ResetButtonLink> </footer> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleButton.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleButton.tsx index 5882d70b307..6c8e466d200 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleButton.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleButton.tsx @@ -22,9 +22,7 @@ import CustomRuleFormModal from './CustomRuleFormModal'; import { RuleDetails } from '../../../app/types'; interface Props { - children: ( - props: { onClick: (event: React.SyntheticEvent<HTMLButtonElement>) => void } - ) => React.ReactNode; + children: (props: { onClick: () => void }) => React.ReactNode; customRule?: RuleDetails; onDone: (newRuleDetails: RuleDetails) => void; organization: string | undefined; @@ -47,9 +45,7 @@ export default class CustomRuleButton extends React.PureComponent<Props, State> this.mounted = false; } - handleClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); + handleClick = () => { this.setState({ modal: true }); }; diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormModal.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormModal.tsx index 426c180a43a..e3076a11b3f 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormModal.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormModal.tsx @@ -29,6 +29,7 @@ import TypeHelper from '../../../components/shared/TypeHelper'; import SeverityHelper from '../../../components/shared/SeverityHelper'; import { createRule, updateRule } from '../../../api/rules'; import { csvEscape } from '../../../helpers/csv'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; interface Props { customRule?: RuleDetails; @@ -84,12 +85,6 @@ export default class CustomRuleFormModal extends React.PureComponent<Props, Stat this.mounted = false; } - handleCancelClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); - this.props.onClose(); - }; - prepareRequest = () => { /* eslint-disable camelcase */ const { customRule, organization, templateRule } = this.props; @@ -248,11 +243,11 @@ export default class CustomRuleFormModal extends React.PureComponent<Props, Stat clearable={false} disabled={this.state.submitting} onChange={this.handleTypeChange} + optionRenderer={this.renderTypeOption} options={TYPES.map(type => ({ label: translate('issue.type', type), value: type }))} - optionRenderer={this.renderTypeOption} searchable={false} value={this.state.type} valueRenderer={this.renderTypeOption} @@ -274,11 +269,11 @@ export default class CustomRuleFormModal extends React.PureComponent<Props, Stat clearable={false} disabled={this.state.submitting} onChange={this.handleSeverityChange} + optionRenderer={this.renderSeverityOption} options={SEVERITIES.map(severity => ({ label: translate('severity', severity), value: severity }))} - optionRenderer={this.renderSeverityOption} searchable={false} value={this.state.severity} valueRenderer={this.renderSeverityOption} @@ -345,21 +340,19 @@ export default class CustomRuleFormModal extends React.PureComponent<Props, Stat renderSubmitButton = () => { if (this.state.reactivating) { return ( - <button + <SubmitButton disabled={this.state.submitting} - id="coding-rules-custom-rule-creation-reactivate" - type="submit"> + id="coding-rules-custom-rule-creation-reactivate"> {translate('coding_rules.reactivate')} - </button> + </SubmitButton> ); } else { return ( - <button + <SubmitButton disabled={this.state.submitting} - id="coding-rules-custom-rule-creation-create" - type="submit"> + id="coding-rules-custom-rule-creation-create"> {translate(this.props.customRule ? 'save' : 'create')} - </button> + </SubmitButton> ); } }; @@ -399,14 +392,12 @@ export default class CustomRuleFormModal extends React.PureComponent<Props, Stat <div className="modal-foot"> {submitting && <i className="spinner spacer-right" />} {this.renderSubmitButton()} - <button - className="button-link" + <ResetButtonLink disabled={submitting} id="coding-rules-custom-rule-creation-cancel" - onClick={this.handleCancelClick} - type="reset"> + onClick={this.props.onClose}> {translate('cancel')} - </button> + </ResetButtonLink> </div> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RemoveExtendedDescriptionModal.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RemoveExtendedDescriptionModal.tsx index 95a7ee29f44..d3a15539858 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RemoveExtendedDescriptionModal.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RemoveExtendedDescriptionModal.tsx @@ -19,6 +19,7 @@ */ import * as React from 'react'; import SimpleModal from '../../../components/controls/SimpleModal'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; interface Props { @@ -30,8 +31,8 @@ export default function RemoveExtendedDescriptionModal({ onCancel, onSubmit }: P const header = translate('coding_rules.remove_extended_description'); return ( <SimpleModal header={header} onClose={onCancel} onSubmit={onSubmit}> - {({ onCloseClick, onSubmitClick, submitting }) => ( - <> + {({ onCloseClick, onFormSubmit, submitting }) => ( + <form onSubmit={onFormSubmit}> <header className="modal-head"> <h2>{header}</h2> </header> @@ -42,18 +43,15 @@ export default function RemoveExtendedDescriptionModal({ onCancel, onSubmit }: P <footer className="modal-foot"> {submitting && <i className="spinner spacer-right" />} - <button + <SubmitButton className="button-red" disabled={submitting} - id="coding-rules-detail-extend-description-remove-submit" - onClick={onSubmitClick}> + id="coding-rules-detail-extend-description-remove-submit"> {translate('remove')} - </button> - <a href="#" onClick={onCloseClick}> - {translate('cancel')} - </a> + </SubmitButton> + <ResetButtonLink onClick={onCloseClick}>{translate('cancel')}</ResetButtonLink> </footer> - </> + </form> )} </SimpleModal> ); diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx index 331e1c908e2..e951e3b9d44 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx @@ -31,6 +31,7 @@ import { getRuleDetails, deleteRule, updateRule } from '../../../api/rules'; import { RuleActivation, RuleDetails as IRuleDetails } from '../../../app/types'; import DeferredSpinner from '../../../components/common/DeferredSpinner'; import ConfirmButton from '../../../components/controls/ConfirmButton'; +import { Button } from '../../../components/ui/buttons'; import { translate, translateWithParameters } from '../../../helpers/l10n'; interface Props { @@ -183,12 +184,12 @@ export default class RuleDetails extends React.PureComponent<Props, State> { organization={organization} templateRule={ruleDetails}> {({ onClick }) => ( - <button + <Button className="js-edit-custom" id="coding-rules-detail-custom-rule-change" onClick={onClick}> {translate('edit')} - </button> + </Button> )} </CustomRuleButton> <ConfirmButton @@ -201,12 +202,12 @@ export default class RuleDetails extends React.PureComponent<Props, State> { modalHeader={translate('coding_rules.delete_rule')} onConfirm={this.handleDelete}> {({ onClick }) => ( - <button + <Button className="button-red spacer-left js-delete" id="coding-rules-detail-rule-delete" onClick={onClick}> {translate('delete')} - </button> + </Button> )} </ConfirmButton> </div> diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsCustomRules.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsCustomRules.tsx index d080327593f..ac0e20cafed 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsCustomRules.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsCustomRules.tsx @@ -26,6 +26,7 @@ import { Rule, RuleDetails } from '../../../app/types'; import DeferredSpinner from '../../../components/common/DeferredSpinner'; import ConfirmButton from '../../../components/controls/ConfirmButton'; import SeverityHelper from '../../../components/shared/SeverityHelper'; +import { Button } from '../../../components/ui/buttons'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { getRuleUrl } from '../../../helpers/urls'; @@ -100,7 +101,7 @@ export default class RuleDetailsCustomRules extends React.PureComponent<Props, S }; renderRule = (rule: Rule) => ( - <tr key={rule.key} data-rule={rule.key}> + <tr data-rule={rule.key} key={rule.key}> <td className="coding-rules-detail-list-name"> <Link to={getRuleUrl(rule.key, this.props.organization)}>{rule.name}</Link> </td> @@ -132,9 +133,9 @@ export default class RuleDetailsCustomRules extends React.PureComponent<Props, S modalHeader={translate('coding_rules.delete_rule')} onConfirm={this.handleRuleDelete}> {({ onClick }) => ( - <button className="button-red js-delete-custom-rule" onClick={onClick}> + <Button className="button-red js-delete-custom-rule" onClick={onClick}> {translate('delete')} - </button> + </Button> )} </ConfirmButton> </td> @@ -158,16 +159,16 @@ export default class RuleDetailsCustomRules extends React.PureComponent<Props, S organization={this.props.organization} templateRule={this.props.ruleDetails}> {({ onClick }) => ( - <button className="js-create-custom-rule spacer-left" onClick={onClick}> + <Button className="js-create-custom-rule spacer-left" onClick={onClick}> {translate('coding_rules.create')} - </button> + </Button> )} </CustomRuleButton> )} <DeferredSpinner loading={loading}> {rules.length > 0 && ( - <table id="coding-rules-detail-custom-rules" className="coding-rules-detail-list"> + <table className="coding-rules-detail-list" id="coding-rules-detail-custom-rules"> <tbody>{sortBy(rules, rule => rule.name).map(this.renderRule)}</tbody> </table> )} diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsDescription.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsDescription.tsx index b21425fa10a..c353a8c5c77 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsDescription.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsDescription.tsx @@ -22,6 +22,7 @@ import RemoveExtendedDescriptionModal from './RemoveExtendedDescriptionModal'; import { updateRule } from '../../../api/rules'; import { RuleDetails } from '../../../app/types'; import MarkdownTips from '../../../components/common/MarkdownTips'; +import { Button, ResetButtonLink } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; interface Props { @@ -58,21 +59,15 @@ export default class RuleDetailsDescription extends React.PureComponent<Props, S handleDescriptionChange = (event: React.SyntheticEvent<HTMLTextAreaElement>) => this.setState({ description: event.currentTarget.value }); - handleCancelClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); + handleCancelClick = () => { this.setState({ descriptionForm: false }); }; - handleSaveClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); + handleSaveClick = () => { this.updateDescription(this.state.description); }; - handleRemoveDescriptionClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); + handleRemoveDescriptionClick = () => { this.setState({ removeDescriptionModal: true }); }; @@ -107,9 +102,7 @@ export default class RuleDetailsDescription extends React.PureComponent<Props, S ); }; - handleExtendDescriptionClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); + handleExtendDescriptionClick = () => { this.setState({ // set description` to the current `mdNote` each time the form is open description: this.props.ruleDetails.mdNote || '', @@ -126,11 +119,11 @@ export default class RuleDetailsDescription extends React.PureComponent<Props, S /> )} {this.props.canWrite && ( - <button + <Button id="coding-rules-detail-extend-description" onClick={this.handleExtendDescriptionClick}> {translate('coding_rules.extend_description')} - </button> + </Button> )} </div> ); @@ -153,21 +146,21 @@ export default class RuleDetailsDescription extends React.PureComponent<Props, S </tr> <tr> <td> - <button + <Button disabled={this.state.submitting} id="coding-rules-detail-extend-description-submit" onClick={this.handleSaveClick}> {translate('save')} - </button> + </Button> {this.props.ruleDetails.mdNote !== undefined && ( <> - <button + <Button className="button-red spacer-left" disabled={this.state.submitting} id="coding-rules-detail-extend-description-remove" onClick={this.handleRemoveDescriptionClick}> {translate('remove')} - </button> + </Button> {this.state.removeDescriptionModal && ( <RemoveExtendedDescriptionModal onCancel={this.handleCancelRemoving} @@ -176,13 +169,13 @@ export default class RuleDetailsDescription extends React.PureComponent<Props, S )} </> )} - <button - className="spacer-left button-link" + <ResetButtonLink + className="spacer-left" disabled={this.state.submitting} id="coding-rules-detail-extend-description-cancel" onClick={this.handleCancelClick}> {translate('cancel')} - </button> + </ResetButtonLink> {this.state.submitting && <i className="spinner spacer-left" />} </td> <td className="text-right"> diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx index 310af7e4bee..fa43eebc44a 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx @@ -33,6 +33,7 @@ import SeverityHelper from '../../../components/shared/SeverityHelper'; import BubblePopupHelper from '../../../components/common/BubblePopupHelper'; import TagsList from '../../../components/tags/TagsList'; import DateFormatter from '../../../components/intl/DateFormatter'; +import { Button } from '../../../components/ui/buttons'; interface Props { canWrite: boolean | undefined; @@ -50,9 +51,7 @@ interface State { export default class RuleDetailsMeta extends React.PureComponent<Props, State> { state: State = { tagsPopup: false }; - handleTagsClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); + handleTagsClick = () => { this.setState(state => ({ tagsPopup: !state.tagsPopup })); }; @@ -106,7 +105,6 @@ export default class RuleDetailsMeta extends React.PureComponent<Props, State> { {this.props.canWrite ? ( <BubblePopupHelper isOpen={this.state.tagsPopup} - position="bottomleft" popup={ <RuleDetailsTagsPopup organization={this.props.organization} @@ -115,13 +113,14 @@ export default class RuleDetailsMeta extends React.PureComponent<Props, State> { tags={tags} /> } + position="bottomleft" togglePopup={this.handleTagsPopupToggle}> - <button className="button-link" onClick={this.handleTagsClick}> + <Button className="button-link" onClick={this.handleTagsClick}> <TagsList allowUpdate={canWrite} tags={allTags.length > 0 ? allTags : [translate('coding_rules.no_tags')]} /> - </button> + </Button> </BubblePopupHelper> ) : ( <TagsList diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx index 551b6d1402a..bb80abd464f 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx @@ -30,6 +30,7 @@ import { getQualityProfileUrl } from '../../../helpers/urls'; import BuiltInQualityProfileBadge from '../../quality-profiles/components/BuiltInQualityProfileBadge'; import Tooltip from '../../../components/controls/Tooltip'; import SeverityHelper from '../../../components/shared/SeverityHelper'; +import { Button } from '../../../components/ui/buttons'; interface Props { activations: RuleActivation[] | undefined; @@ -193,11 +194,11 @@ export default class RuleDetailsProfiles extends React.PureComponent<Props, Stat modalHeader={translate('coding_rules.revert_to_parent_definition')} onConfirm={this.handleRevert}> {({ onClick }) => ( - <button + <Button className="coding-rules-detail-quality-profile-revert button-red spacer-left" onClick={onClick}> {translate('coding_rules.revert_to_parent_definition')} - </button> + </Button> )} </ConfirmButton> ) @@ -209,11 +210,11 @@ export default class RuleDetailsProfiles extends React.PureComponent<Props, Stat modalHeader={translate('coding_rules.deactivate')} onConfirm={this.handleDeactivate}> {({ onClick }) => ( - <button + <Button className="coding-rules-detail-quality-profile-deactivate button-red spacer-left" onClick={onClick}> {translate('coding_rules.deactivate')} - </button> + </Button> )} </ConfirmButton> )} @@ -233,7 +234,7 @@ export default class RuleDetailsProfiles extends React.PureComponent<Props, Stat const parentActivation = activations.find(x => x.qProfile === profile.parentKey); return ( - <tr key={profile.key} data-profile={profile.key}> + <tr data-profile={profile.key} key={profile.key}> <td className="coding-rules-detail-quality-profile-name"> <Link to={getQualityProfileUrl(profile.name, profile.language, this.props.organization)}> {profile.name} @@ -281,8 +282,8 @@ export default class RuleDetailsProfiles extends React.PureComponent<Props, Stat {activations.length > 0 && ( <table - id="coding-rules-detail-quality-profiles" - className="coding-rules-detail-quality-profiles width100"> + className="coding-rules-detail-quality-profiles width100" + id="coding-rules-detail-quality-profiles"> <tbody>{activations.map(this.renderActivation)}</tbody> </table> )} diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx index 3f9852c418d..3b70690ca5f 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx @@ -28,6 +28,7 @@ import { Rule, RuleInheritance } from '../../../app/types'; import ConfirmButton from '../../../components/controls/ConfirmButton'; import Tooltip from '../../../components/controls/Tooltip'; import SeverityIcon from '../../../components/shared/SeverityIcon'; +import { Button } from '../../../components/ui/buttons'; import IssueTypeIcon from '../../../components/ui/IssueTypeIcon'; import { translate, translateWithParameters } from '../../../helpers/l10n'; @@ -141,18 +142,18 @@ export default class RuleListItem extends React.PureComponent<Props> { modalHeader={translate('coding_rules.deactivate')} onConfirm={this.handleDeactivate}> {({ onClick }) => ( - <button + <Button className="coding-rules-detail-quality-profile-deactivate button-red" onClick={onClick}> {translate('coding_rules.deactivate')} - </button> + </Button> )} </ConfirmButton> ) : ( <Tooltip overlay={translate('coding_rules.can_not_deactivate')} placement="left"> - <button className="coding-rules-detail-quality-profile-deactivate button-red disabled"> + <Button className="coding-rules-detail-quality-profile-deactivate button-red disabled"> {translate('coding_rules.deactivate')} - </button> + </Button> </Tooltip> ); }; diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/CreateButton.tsx b/server/sonar-web/src/main/js/apps/custom-measures/components/CreateButton.tsx index feea67a5ab4..c8a7dd6049c 100644 --- a/server/sonar-web/src/main/js/apps/custom-measures/components/CreateButton.tsx +++ b/server/sonar-web/src/main/js/apps/custom-measures/components/CreateButton.tsx @@ -19,6 +19,7 @@ */ import * as React from 'react'; import Form from './Form'; +import { Button } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; interface Props { @@ -55,9 +56,9 @@ export default class CreateButton extends React.PureComponent<Props, State> { render() { return ( <> - <button id="custom-measures-create" onClick={this.handleClick} type="button"> + <Button id="custom-measures-create" onClick={this.handleClick}> {translate('create')} - </button> + </Button> {this.state.modal && ( <Form confirmButtonText={translate('create')} diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/Form.tsx b/server/sonar-web/src/main/js/apps/custom-measures/components/Form.tsx index 6b814dd735a..3f584725f8a 100644 --- a/server/sonar-web/src/main/js/apps/custom-measures/components/Form.tsx +++ b/server/sonar-web/src/main/js/apps/custom-measures/components/Form.tsx @@ -23,6 +23,7 @@ import { CustomMeasure, Metric } from '../../../app/types'; import DeferredSpinner from '../../../components/common/DeferredSpinner'; import Select from '../../../components/controls/Select'; import SimpleModal from '../../../components/controls/SimpleModal'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; interface Props { @@ -186,20 +187,17 @@ export default class Form extends React.PureComponent<Props, State> { <footer className="modal-foot"> <DeferredSpinner className="spacer-right" loading={submitting} /> - <button + <SubmitButton disabled={forbidSubmitting || submitting} - id="create-custom-measure-submit" - type="submit"> + id="create-custom-measure-submit"> {this.props.confirmButtonText} - </button> - <button - className="button-link" + </SubmitButton> + <ResetButtonLink disabled={submitting} id="create-custom-measure-cancel" - onClick={onCloseClick} - type="reset"> + onClick={onCloseClick}> {translate('cancel')} - </button> + </ResetButtonLink> </footer> </form> )} diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Form-test.tsx b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Form-test.tsx index e6252db478f..1cfc6991abd 100644 --- a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Form-test.tsx +++ b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Form-test.tsx @@ -62,6 +62,6 @@ it('should render form', async () => { expect(onClose).toBeCalled(); onClose.mockClear(); - click(form.find('button[type="reset"]')); + click(form.find('ResetButtonLink')); expect(onClose).toBeCalled(); }); diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/CreateButton-test.tsx.snap b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/CreateButton-test.tsx.snap index 78c55c67890..8973f165fae 100644 --- a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/CreateButton-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/CreateButton-test.tsx.snap @@ -2,25 +2,23 @@ exports[`should create new custom measure 1`] = ` <React.Fragment> - <button + <Button id="custom-measures-create" onClick={[Function]} - type="button" > create - </button> + </Button> </React.Fragment> `; exports[`should create new custom measure 2`] = ` <React.Fragment> - <button + <Button id="custom-measures-create" onClick={[Function]} - type="button" > create - </button> + </Button> <Form confirmButtonText="create" header="custom_measures.create_custom_measure" diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/Form-test.tsx.snap b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/Form-test.tsx.snap index 17057ba3704..858183d2219 100644 --- a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/Form-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/Form-test.tsx.snap @@ -83,22 +83,19 @@ exports[`should render form 1`] = ` loading={false} timeout={100} /> - <button + <SubmitButton disabled={true} id="create-custom-measure-submit" - type="submit" > confirmButtonText - </button> - <button - className="button-link" + </SubmitButton> + <ResetButtonLink disabled={false} id="create-custom-measure-cancel" onClick={[Function]} - type="reset" > cancel - </button> + </ResetButtonLink> </footer> </form> </Modal> @@ -197,22 +194,19 @@ exports[`should render form 2`] = ` loading={false} timeout={100} /> - <button + <SubmitButton disabled={false} id="create-custom-measure-submit" - type="submit" > confirmButtonText - </button> - <button - className="button-link" + </SubmitButton> + <ResetButtonLink disabled={false} id="create-custom-measure-cancel" onClick={[Function]} - type="reset" > cancel - </button> + </ResetButtonLink> </footer> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/CreateButton.tsx b/server/sonar-web/src/main/js/apps/custom-metrics/components/CreateButton.tsx index e3b67e82d84..582f306f40f 100644 --- a/server/sonar-web/src/main/js/apps/custom-metrics/components/CreateButton.tsx +++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/CreateButton.tsx @@ -19,6 +19,7 @@ */ import * as React from 'react'; import Form, { MetricProps } from './Form'; +import { Button } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; interface Props { @@ -56,9 +57,9 @@ export default class CreateButton extends React.PureComponent<Props, State> { render() { return ( <> - <button id="metrics-create" onClick={this.handleClick}> + <Button id="metrics-create" onClick={this.handleClick}> {translate('custom_metrics.create_metric')} - </button> + </Button> {this.state.modal && ( <Form confirmButtonText={translate('create')} diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/Form.tsx b/server/sonar-web/src/main/js/apps/custom-metrics/components/Form.tsx index 0922d9a9b40..89c4fc9ba20 100644 --- a/server/sonar-web/src/main/js/apps/custom-metrics/components/Form.tsx +++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/Form.tsx @@ -23,6 +23,7 @@ import DeferredSpinner from '../../../components/common/DeferredSpinner'; import SimpleModal from '../../../components/controls/SimpleModal'; import { translate } from '../../../helpers/l10n'; import Select, { Creatable } from '../../../components/controls/Select'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; export interface MetricProps { description: string; @@ -168,17 +169,15 @@ export default class Form extends React.PureComponent<Props, State> { <footer className="modal-foot"> <DeferredSpinner className="spacer-right" loading={submitting} /> - <button disabled={submitting} id="create-metric-submit" type="submit"> + <SubmitButton disabled={submitting} id="create-metric-submit"> {this.props.confirmButtonText} - </button> - <button - className="button-link" + </SubmitButton> + <ResetButtonLink disabled={submitting} id="create-metric-cancel" - onClick={onCloseClick} - type="reset"> + onClick={onCloseClick}> {translate('cancel')} - </button> + </ResetButtonLink> </footer> </form> )} diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/Form-test.tsx b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/Form-test.tsx index a2b633095d9..1e828bc139e 100644 --- a/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/Form-test.tsx +++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/Form-test.tsx @@ -54,6 +54,6 @@ it('should render form', async () => { expect(onClose).toBeCalled(); onClose.mockClear(); - click(wrapper.find('button[type="reset"]')); + click(wrapper.find('ResetButtonLink')); expect(onClose).toBeCalled(); }); diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/CreateButton-test.tsx.snap b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/CreateButton-test.tsx.snap index fcc3412a7a6..46c2ffc4706 100644 --- a/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/CreateButton-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/CreateButton-test.tsx.snap @@ -2,23 +2,23 @@ exports[`should create new group 1`] = ` <React.Fragment> - <button + <Button id="metrics-create" onClick={[Function]} > custom_metrics.create_metric - </button> + </Button> </React.Fragment> `; exports[`should create new group 2`] = ` <React.Fragment> - <button + <Button id="metrics-create" onClick={[Function]} > custom_metrics.create_metric - </button> + </Button> <Form confirmButtonText="create" domains={ diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/Form-test.tsx.snap b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/Form-test.tsx.snap index 0eea472611e..f0baf33640e 100644 --- a/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/Form-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/Form-test.tsx.snap @@ -144,22 +144,19 @@ exports[`should render form 1`] = ` loading={false} timeout={100} /> - <button + <SubmitButton disabled={false} id="create-metric-submit" - type="submit" > confirmButtonText - </button> - <button - className="button-link" + </SubmitButton> + <ResetButtonLink disabled={false} id="create-metric-cancel" onClick={[Function]} - type="reset" > cancel - </button> + </ResetButtonLink> </footer> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/groups/components/EditMembers.tsx b/server/sonar-web/src/main/js/apps/groups/components/EditMembers.tsx index a83ee573e55..9b62320e86b 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/EditMembers.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/EditMembers.tsx @@ -23,7 +23,7 @@ import { Group } from '../../../app/types'; import Modal from '../../../components/controls/Modal'; import BulletListIcon from '../../../components/icons-components/BulletListIcon'; import SelectList from '../../../components/SelectList'; -import { ButtonIcon } from '../../../components/ui/buttons'; +import { ButtonIcon, ResetButtonLink } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; import { getBaseUrl } from '../../../helpers/urls'; @@ -64,12 +64,6 @@ export default class EditMembers extends React.PureComponent<Props, State> { } }; - handleCloseClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); - event.currentTarget.blur(); - this.handleModalClose(); - }; - renderSelectList = () => { if (this.container) { const extra = { name: this.props.group.name, organization: this.props.organization }; @@ -114,9 +108,7 @@ export default class EditMembers extends React.PureComponent<Props, State> { </div> <footer className="modal-foot"> - <button className="button-link" onClick={this.handleCloseClick} type="reset"> - {translate('Done')} - </button> + <ResetButtonLink onClick={this.handleModalClose}>{translate('Done')}</ResetButtonLink> </footer> </Modal> )} diff --git a/server/sonar-web/src/main/js/apps/groups/components/Form.tsx b/server/sonar-web/src/main/js/apps/groups/components/Form.tsx index 23452cef400..8d1e4fc8434 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/Form.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/Form.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import { Group } from '../../../app/types'; import DeferredSpinner from '../../../components/common/DeferredSpinner'; import SimpleModal from '../../../components/controls/SimpleModal'; +import { ResetButtonLink, SubmitButton } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; interface Props { @@ -102,12 +103,8 @@ export default class Form extends React.PureComponent<Props, State> { <footer className="modal-foot"> <DeferredSpinner className="spacer-right" loading={submitting} /> - <button disabled={submitting} type="submit"> - {this.props.confirmButtonText} - </button> - <button className="button-link" onClick={onCloseClick} type="reset"> - {translate('cancel')} - </button> + <SubmitButton disabled={submitting}>{this.props.confirmButtonText}</SubmitButton> + <ResetButtonLink onClick={onCloseClick}>{translate('cancel')}</ResetButtonLink> </footer> </form> )} diff --git a/server/sonar-web/src/main/js/apps/groups/components/Header.tsx b/server/sonar-web/src/main/js/apps/groups/components/Header.tsx index 36f13bad647..cd2be39f671 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/Header.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/Header.tsx @@ -20,6 +20,7 @@ import * as React from 'react'; import Form from './Form'; import DeferredSpinner from '../../../components/common/DeferredSpinner'; +import { Button } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; interface Props { @@ -43,9 +44,7 @@ export default class Header extends React.PureComponent<Props, State> { this.mounted = false; } - handleCreateClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); + handleCreateClick = () => { this.setState({ createModal: true }); }; @@ -68,9 +67,9 @@ export default class Header extends React.PureComponent<Props, State> { <DeferredSpinner loading={this.props.loading} /> <div className="page-actions"> - <button id="groups-create" onClick={this.handleCreateClick}> + <Button id="groups-create" onClick={this.handleCreateClick}> {translate('groups.create_group')} - </button> + </Button> </div> <p className="page-description">{translate('user_groups.page.description')}</p> diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditGroup-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditGroup-test.tsx index 578b79d356d..d650c321233 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditGroup-test.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditGroup-test.tsx @@ -31,7 +31,7 @@ it('should edit group', () => { <EditGroup group={group} onEdit={onEdit}> {props => { ({ onClick } = props); - return <button />; + return <div />; }} </EditGroup> ); diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembers-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembers-test.tsx index a4a9297d329..a480e466c8a 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembers-test.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembers-test.tsx @@ -29,11 +29,10 @@ it('should edit members', () => { const wrapper = shallow(<EditMembers group={group} onEdit={onEdit} organization="org" />); expect(wrapper).toMatchSnapshot(); - wrapper.find('ButtonIcon').prop<Function>('onClick')(); - wrapper.update(); + click(wrapper.find('ButtonIcon')); expect(wrapper).toMatchSnapshot(); - click(wrapper.find('button[type="reset"]')); + click(wrapper.find('ResetButtonLink')); expect(onEdit).toBeCalled(); expect(wrapper).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/Form-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/Form-test.tsx index b89f7249231..744b90c813d 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/Form-test.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/Form-test.tsx @@ -44,6 +44,6 @@ it('should render form', async () => { expect(onClose).toBeCalled(); onClose.mockClear(); - click(wrapper.find('button[type="reset"]')); + click(wrapper.find('ResetButtonLink')); expect(onClose).toBeCalled(); }); diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/Header-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/Header-test.tsx index a237e8bafec..416b1585e11 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/Header-test.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/Header-test.tsx @@ -27,7 +27,7 @@ it('should create new group', () => { const wrapper = shallow(<Header loading={false} onCreate={onCreate} />); expect(wrapper).toMatchSnapshot(); - click(wrapper.find('#groups-create')); + click(wrapper.find('[id="groups-create"]')); expect(wrapper).toMatchSnapshot(); wrapper.find('Form').prop<Function>('onSubmit')({ name: 'foo', description: 'bar' }); diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditGroup-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditGroup-test.tsx.snap index 8b54df832b3..43bc8b832cb 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditGroup-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditGroup-test.tsx.snap @@ -2,13 +2,13 @@ exports[`should edit group 1`] = ` <React.Fragment> - <button /> + <div /> </React.Fragment> `; exports[`should edit group 2`] = ` <React.Fragment> - <button /> + <div /> <Form confirmButtonText="update_verb" group={ @@ -27,6 +27,6 @@ exports[`should edit group 2`] = ` exports[`should edit group 3`] = ` <React.Fragment> - <button /> + <div /> </React.Fragment> `; diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap index 8c846140be8..230836640f5 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap @@ -40,13 +40,11 @@ exports[`should edit members 2`] = ` <footer className="modal-foot" > - <button - className="button-link" + <ResetButtonLink onClick={[Function]} - type="reset" > Done - </button> + </ResetButtonLink> </footer> </Modal> </React.Fragment> diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Form-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Form-test.tsx.snap index e43777b754e..2b0954e09fb 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Form-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Form-test.tsx.snap @@ -67,19 +67,16 @@ exports[`should render form 1`] = ` loading={false} timeout={100} /> - <button + <SubmitButton disabled={false} - type="submit" > confirmButtonText - </button> - <button - className="button-link" + </SubmitButton> + <ResetButtonLink onClick={[Function]} - type="reset" > cancel - </button> + </ResetButtonLink> </footer> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Header-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Header-test.tsx.snap index 6578cf61fe8..546722468c8 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Header-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Header-test.tsx.snap @@ -18,12 +18,12 @@ exports[`should create new group 1`] = ` <div className="page-actions" > - <button + <Button id="groups-create" onClick={[Function]} > groups.create_group - </button> + </Button> </div> <p className="page-description" @@ -52,12 +52,12 @@ exports[`should create new group 2`] = ` <div className="page-actions" > - <button + <Button id="groups-create" onClick={[Function]} > groups.create_group - </button> + </Button> </div> <p className="page-description" diff --git a/server/sonar-web/src/main/js/apps/maintenance/components/App.tsx b/server/sonar-web/src/main/js/apps/maintenance/components/App.tsx index 3bc5e06b616..8f19a13856e 100644 --- a/server/sonar-web/src/main/js/apps/maintenance/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/maintenance/components/App.tsx @@ -22,6 +22,7 @@ import * as classNames from 'classnames'; import { getMigrationStatus, getSystemStatus, migrateDatabase } from '../../../api/system'; import DateFromNow from '../../../components/intl/DateFromNow'; import TimeFormatter from '../../../components/intl/TimeFormatter'; +import { Button } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; import { getBaseUrl } from '../../../helpers/urls'; import '../styles.css'; @@ -113,9 +114,7 @@ export default class App extends React.PureComponent<Props, State> { }, 2500); }; - handleMigrateClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); + handleMigrateClick = () => { migrateDatabase().then( ({ message, startedAt, state }) => { if (this.mounted) { @@ -221,9 +220,9 @@ export default class App extends React.PureComponent<Props, State> { <p className="maintenance-text">{translate('maintenance.upgrade_database.2')}</p> <p className="maintenance-text">{translate('maintenance.upgrade_database.3')}</p> <div className="maintenance-spinner"> - <button id="start-migration" onClick={this.handleMigrateClick} type="button"> + <Button id="start-migration" onClick={this.handleMigrateClick}> {translate('maintenance.upgrade')} - </button> + </Button> </div> </> )} diff --git a/server/sonar-web/src/main/js/apps/maintenance/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/maintenance/components/__tests__/App-test.tsx index 8d4511d09e7..93a6d4ab930 100644 --- a/server/sonar-web/src/main/js/apps/maintenance/components/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/maintenance/components/__tests__/App-test.tsx @@ -120,7 +120,7 @@ describe('Setup Page', () => { await waitAndUpdate(wrapper); expect(wrapper).toMatchSnapshot(); - click(wrapper.find('button')); + click(wrapper.find('Button')); expect(migrateDatabase).toBeCalled(); await waitAndUpdate(wrapper); expect(wrapper).toMatchSnapshot(); diff --git a/server/sonar-web/src/main/js/apps/maintenance/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/maintenance/components/__tests__/__snapshots__/App-test.tsx.snap index 784a8eb1969..d054e906e8c 100644 --- a/server/sonar-web/src/main/js/apps/maintenance/components/__tests__/__snapshots__/App-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/maintenance/components/__tests__/__snapshots__/App-test.tsx.snap @@ -340,13 +340,12 @@ exports[`Setup Page should start migration 1`] = ` <div className="maintenance-spinner" > - <button + <Button id="start-migration" onClick={[Function]} - type="button" > maintenance.upgrade - </button> + </Button> </div> </React.Fragment> </div> diff --git a/server/sonar-web/src/main/js/apps/marketplace/PendingActions.tsx b/server/sonar-web/src/main/js/apps/marketplace/PendingActions.tsx index bbe8e3c48c3..d392d5efcc3 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/PendingActions.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/PendingActions.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import RestartForm from '../../components/common/RestartForm'; import { cancelPendingPlugins, PluginPending } from '../../api/plugins'; +import { Button } from '../../components/ui/buttons'; import { translate } from '../../helpers/l10n'; interface Props { @@ -39,8 +40,13 @@ interface State { export default class PendingActions extends React.PureComponent<Props, State> { state: State = { openRestart: false }; - handleOpenRestart = () => this.setState({ openRestart: true }); - hanleCloseRestart = () => this.setState({ openRestart: false }); + handleOpenRestart = () => { + this.setState({ openRestart: true }); + }; + + hanleCloseRestart = () => { + this.setState({ openRestart: false }); + }; handleRevert = () => { cancelPendingPlugins().then(this.props.refreshPending, () => {}); @@ -88,12 +94,12 @@ export default class PendingActions extends React.PureComponent<Props, State> { </ul> </div> <div className="pull-right"> - <button className="js-restart little-spacer-right" onClick={this.handleOpenRestart}> + <Button className="js-restart little-spacer-right" onClick={this.handleOpenRestart}> {translate('marketplace.restart')} - </button> - <button className="js-cancel-all button-red" onClick={this.handleRevert}> + </Button> + <Button className="js-cancel-all button-red" onClick={this.handleRevert}> {translate('marketplace.revert')} - </button> + </Button> </div> {this.state.openRestart && <RestartForm onClose={this.hanleCloseRestart} />} </div> diff --git a/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/PendingActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/PendingActions-test.tsx.snap index f68b3b68416..c8688596746 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/PendingActions-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/PendingActions-test.tsx.snap @@ -44,18 +44,18 @@ exports[`should display pending actions 1`] = ` <div className="pull-right" > - <button + <Button className="js-restart little-spacer-right" onClick={[Function]} > marketplace.restart - </button> - <button + </Button> + <Button className="js-cancel-all button-red" onClick={[Function]} > marketplace.revert - </button> + </Button> </div> </div> `; diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx index 10965d760ce..0ad689bc93d 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx @@ -20,6 +20,7 @@ import * as React from 'react'; import EditionBoxBadge from './EditionBoxBadge'; import { Edition, EditionStatus } from '../../../api/marketplace'; +import { Button } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; interface Props { @@ -32,7 +33,9 @@ interface Props { } export default class EditionBox extends React.PureComponent<Props> { - handleAction = () => this.props.onAction(this.props.edition); + handleAction = () => { + this.props.onAction(this.props.edition); + }; render() { const { disableAction, displayAction, edition, editionStatus } = this.props; @@ -48,9 +51,9 @@ export default class EditionBox extends React.PureComponent<Props> { {translate('marketplace.learn_more')} </a> {displayAction && ( - <button disabled={disableAction} onClick={this.handleAction}> + <Button disabled={disableAction} onClick={this.handleAction}> {this.props.actionLabel} - </button> + </Button> )} </div> </div> diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionForm.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionForm.tsx index fb609f9e30a..a4c48287869 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionForm.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionForm.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import LicenseEditionSet from './LicenseEditionSet'; import { Edition, EditionStatus, applyLicense } from '../../../api/marketplace'; import Modal from '../../../components/controls/Modal'; +import { Button, ResetButtonLink } from '../../../components/ui/buttons'; import { translate, translateWithParameters } from '../../../helpers/l10n'; export interface Props { @@ -55,13 +56,7 @@ export default class LicenseEditionForm extends React.PureComponent<Props, State } }; - handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - - handleConfirmClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); + handleConfirmClick = () => { const { license, status } = this.state; if (license && status) { this.setState({ submitting: true }); @@ -102,18 +97,16 @@ export default class LicenseEditionForm extends React.PureComponent<Props, State <footer className="modal-foot"> {submitting && <i className="spinner spacer-right" />} {status && ( - <button + <Button className="js-confirm" - onClick={this.handleConfirmClick} - disabled={!license || submitting}> + disabled={!license || submitting} + onClick={this.handleConfirmClick}> {status === 'AUTOMATIC_INSTALL' ? translate('marketplace.install') : translate('save')} - </button> + </Button> )} - <a className="js-modal-close" href="#" onClick={this.handleCancelClick}> - {translate('cancel')} - </a> + <ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink> </footer> </Modal> ); diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx index 8edda448a25..45ca8e69773 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx @@ -19,10 +19,11 @@ */ import * as React from 'react'; import PluginUpdateButton from './PluginUpdateButton'; +import { isPluginAvailable, isPluginInstalled } from '../utils'; +import { Plugin, installPlugin, updatePlugin, uninstallPlugin } from '../../../api/plugins'; import Checkbox from '../../../components/controls/Checkbox'; import CheckIcon from '../../../components/icons-components/CheckIcon'; -import { Plugin, installPlugin, updatePlugin, uninstallPlugin } from '../../../api/plugins'; -import { isPluginAvailable, isPluginInstalled } from '../utils'; +import { Button } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; interface Props { @@ -150,23 +151,21 @@ export default class PluginActions extends React.PureComponent<Props, State> { update={update} /> ))} - <button + <Button className="js-uninstall button-red little-spacer-left" disabled={loading} - onClick={this.handleUninstall} - type="button"> + onClick={this.handleUninstall}> {translate('marketplace.uninstall')} - </button> + </Button> </div> )} {isPluginAvailable(plugin) && ( - <button + <Button className="js-install" disabled={loading || (plugin.termsAndConditionsUrl != null && !this.state.acceptTerms)} - onClick={this.handleInstall} - type="button"> + onClick={this.handleInstall}> {translate('marketplace.install')} - </button> + </Button> )} </div> ); diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogButton.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogButton.tsx index a1f17963a7e..b542908b5c2 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogButton.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogButton.tsx @@ -19,8 +19,9 @@ */ import * as React from 'react'; import PluginChangeLog from './PluginChangeLog'; -import BubblePopupHelper from '../../../components/common/BubblePopupHelper'; import { Release, Update } from '../../../api/plugins'; +import BubblePopupHelper from '../../../components/common/BubblePopupHelper'; +import { Button } from '../../../components/ui/buttons'; interface Props { release: Release; @@ -34,12 +35,6 @@ interface State { export default class PluginChangeLogButton extends React.PureComponent<Props, State> { state: State = { changelogOpen: false }; - handleChangelogClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.stopPropagation(); - this.toggleChangelog(); - }; - toggleChangelog = (show?: boolean) => { if (show !== undefined) { this.setState({ changelogOpen: show }); @@ -48,17 +43,21 @@ export default class PluginChangeLogButton extends React.PureComponent<Props, St } }; + handleClick = () => { + this.toggleChangelog(); + }; + render() { return ( <div className="display-inline-block little-spacer-left"> - <button + <Button className="button-link js-changelog issue-rule icon-ellipsis-h" - onClick={this.handleChangelogClick} + onClick={this.handleClick} /> <BubblePopupHelper isOpen={this.state.changelogOpen} - position="bottomright" popup={<PluginChangeLog release={this.props.release} update={this.props.update} />} + position="bottomright" togglePopup={this.toggleChangelog} /> </div> diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateButton.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateButton.tsx index b426f048247..8899613e0b9 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateButton.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateButton.tsx @@ -19,6 +19,7 @@ */ import * as React from 'react'; import { Update } from '../../../api/plugins'; +import { Button } from '../../../components/ui/buttons'; import { translateWithParameters } from '../../../helpers/l10n'; interface Props { @@ -28,7 +29,9 @@ interface Props { } export default class PluginUpdateButton extends React.PureComponent<Props> { - handleClick = () => this.props.onClick(this.props.update); + handleClick = () => { + this.props.onClick(this.props.update); + }; render() { const { disabled, update } = this.props; @@ -36,13 +39,12 @@ export default class PluginUpdateButton extends React.PureComponent<Props> { return null; } return ( - <button + <Button className="js-update little-spacer-bottom" disabled={disabled} - onClick={this.handleClick} - type="button"> + onClick={this.handleClick}> {translateWithParameters('marketplace.update_to_x', update.release.version)} - </button> + </Button> ); } } diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/UninstallEditionForm.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/UninstallEditionForm.tsx index ad79e2a31d2..1b9be29c7df 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/UninstallEditionForm.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/UninstallEditionForm.tsx @@ -20,6 +20,7 @@ import * as React from 'react'; import { Edition, EditionStatus, uninstallEdition } from '../../../api/marketplace'; import Modal from '../../../components/controls/Modal'; +import { Button, ResetButtonLink } from '../../../components/ui/buttons'; import { translate, translateWithParameters } from '../../../helpers/l10n'; export interface Props { @@ -45,13 +46,7 @@ export default class UninstallEditionForm extends React.PureComponent<Props, Sta this.mounted = false; } - handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - - handleConfirmClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); + handleConfirmClick = () => { this.setState({ loading: true }); uninstallEdition() .then(() => { @@ -86,12 +81,12 @@ export default class UninstallEditionForm extends React.PureComponent<Props, Sta <footer className="modal-foot"> {loading && <i className="spinner spacer-right" />} - <button disabled={loading} onClick={this.handleConfirmClick}> + <Button disabled={loading} onClick={this.handleConfirmClick}> {translate('marketplace.downgrade')} - </button> - <a className="js-modal-close" href="#" onClick={this.handleCancelClick}> + </Button> + <ResetButtonLink className="js-modal-close" onClick={this.props.onClose}> {translate('cancel')} - </a> + </ResetButtonLink> </footer> </Modal> ); diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionForm-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionForm-test.tsx index 42db6f8c02b..9814570cbf8 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionForm-test.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionForm-test.tsx @@ -54,22 +54,19 @@ it('should correctly change the button based on the status and license', () => { (wrapper.instance() as LicenseEditionForm).mounted = true; wrapper.setState({ license: 'mylicense', status: 'NO_INSTALL' }); - button = wrapper.find('button'); - expect(button.text()).toBe('save'); - expect(button.prop('disabled')).toBeFalsy(); + button = wrapper.find('Button'); + expect(button).toMatchSnapshot(); wrapper.setState({ license: undefined, status: 'MANUAL_INSTALL' }); - button = wrapper.find('button'); - expect(button.text()).toBe('save'); - expect(button.prop('disabled')).toBeTruthy(); + button = wrapper.find('Button'); + expect(button).toMatchSnapshot(); wrapper.setState({ status: 'AUTOMATIC_INSTALL' }); - button = wrapper.find('button'); - expect(button.text()).toContain('install'); - expect(button.prop('disabled')).toBeTruthy(); + button = wrapper.find('Button'); + expect(button).toMatchSnapshot(); wrapper.setState({ license: 'mylicense' }); - expect(wrapper.find('button').prop('disabled')).toBeFalsy(); + expect(wrapper.find('Button').prop('disabled')).toBeFalsy(); }); it('should update the edition status after install', async () => { @@ -78,7 +75,7 @@ it('should update the edition status after install', async () => { const form = wrapper.instance() as LicenseEditionForm; form.handleLicenseChange('mylicense', 'AUTOMATIC_INSTALL'); wrapper.update(); - click(wrapper.find('button')); + click(wrapper.find('Button')); expect(applyLicense).toHaveBeenCalledWith({ license: 'mylicense' }); await new Promise(setImmediate); expect(updateEditionStatus).toHaveBeenCalledWith({ diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/UninstallEditionForm-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/UninstallEditionForm-test.tsx index 11256063574..f8dbede4cb2 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/UninstallEditionForm-test.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/UninstallEditionForm-test.tsx @@ -50,7 +50,7 @@ it('should update the edition status after uninstall', async () => { const updateEditionStatus = jest.fn(); const wrapper = getWrapper({ updateEditionStatus }); (wrapper.instance() as UninstallEditionForm).mounted = true; - click(wrapper.find('button')); + click(wrapper.find('Button')); expect(uninstallEdition).toHaveBeenCalled(); await new Promise(setImmediate); expect(updateEditionStatus).toHaveBeenCalledWith({ diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap index 9fa9b253102..b9925cec2c1 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap @@ -70,12 +70,12 @@ exports[`should disable action button 1`] = ` > marketplace.learn_more </a> - <button + <Button disabled={true} onClick={[Function]} > action - </button> + </Button> </div> </div> `; @@ -113,12 +113,12 @@ exports[`should display the edition 1`] = ` > marketplace.learn_more </a> - <button + <Button disabled={false} onClick={[Function]} > action - </button> + </Button> </div> </div> `; diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionForm-test.tsx.snap index 9c3d5970d11..c0dcce3c40a 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionForm-test.tsx.snap @@ -1,5 +1,35 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`should correctly change the button based on the status and license 1`] = ` +<Button + className="js-confirm" + disabled={false} + onClick={[Function]} +> + save +</Button> +`; + +exports[`should correctly change the button based on the status and license 2`] = ` +<Button + className="js-confirm" + disabled={true} + onClick={[Function]} +> + save +</Button> +`; + +exports[`should correctly change the button based on the status and license 3`] = ` +<Button + className="js-confirm" + disabled={true} + onClick={[Function]} +> + marketplace.install +</Button> +`; + exports[`should display correctly 1`] = ` <Modal contentLabel="marketplace.upgrade_to_x.Foo" @@ -41,13 +71,11 @@ exports[`should display correctly 1`] = ` <footer className="modal-foot" > - <a - className="js-modal-close" - href="#" - onClick={[Function]} + <ResetButtonLink + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </Modal> `; diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/UninstallEditionForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/UninstallEditionForm-test.tsx.snap index 06d57cb8f6f..491d59eb8f5 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/UninstallEditionForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/UninstallEditionForm-test.tsx.snap @@ -22,19 +22,18 @@ exports[`should display correctly 1`] = ` <footer className="modal-foot" > - <button + <Button disabled={false} onClick={[Function]} > marketplace.downgrade - </button> - <a + </Button> + <ResetButtonLink className="js-modal-close" - href="#" - onClick={[Function]} + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </Modal> `; diff --git a/server/sonar-web/src/main/js/apps/overview/badges/BadgeButton.tsx b/server/sonar-web/src/main/js/apps/overview/badges/BadgeButton.tsx index a43aa00e075..832e0e43bc1 100644 --- a/server/sonar-web/src/main/js/apps/overview/badges/BadgeButton.tsx +++ b/server/sonar-web/src/main/js/apps/overview/badges/BadgeButton.tsx @@ -20,6 +20,7 @@ import * as React from 'react'; import * as classNames from 'classnames'; import { BadgeType } from './utils'; +import { Button } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; interface Props { @@ -30,15 +31,17 @@ interface Props { } export default class BadgeButton extends React.PureComponent<Props> { - handleClick = () => this.props.onClick(this.props.type); + handleClick = () => { + this.props.onClick(this.props.type); + }; render() { const { selected, type, url } = this.props; const width = type !== BadgeType.measure ? '128px' : undefined; return ( - <button className={classNames('badge-button', { selected })} onClick={this.handleClick}> - <img src={url} alt={translate('overview.badges', type, 'alt')} width={width} /> - </button> + <Button className={classNames('badge-button', { selected })} onClick={this.handleClick}> + <img alt={translate('overview.badges', type, 'alt')} src={url} width={width} /> + </Button> ); } } diff --git a/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx b/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx index 4a4e572f7c3..fc98e4eb71a 100644 --- a/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx +++ b/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx @@ -26,6 +26,7 @@ import { Metric } from '../../../app/types'; import Modal from '../../../components/controls/Modal'; import { translate } from '../../../helpers/l10n'; import './styles.css'; +import { Button, ResetButtonLink } from '../../../components/ui/buttons'; interface Props { branch?: string; @@ -46,18 +47,21 @@ export default class BadgesModal extends React.PureComponent<Props, State> { badgeOptions: { color: 'white', metric: 'alert_status' } }; - handleClose = () => this.setState({ open: false }); - - handleOpen = () => this.setState({ open: true }); + handleClose = () => { + this.setState({ open: false }); + }; - handleSelectBadge = (selectedType: BadgeType) => this.setState({ selectedType }); + handleOpen = () => { + this.setState({ open: true }); + }; - handleUpdateOptions = (options: Partial<BadgeOptions>) => - this.setState(state => ({ - badgeOptions: { ...state.badgeOptions, ...options } - })); + handleSelectBadge = (selectedType: BadgeType) => { + this.setState({ selectedType }); + }; - handleCancelClick = () => this.handleClose(); + handleUpdateOptions = (options: Partial<BadgeOptions>) => { + this.setState(state => ({ badgeOptions: { ...state.badgeOptions, ...options } })); + }; render() { const { branch, project } = this.props; @@ -66,9 +70,9 @@ export default class BadgesModal extends React.PureComponent<Props, State> { const fullBadgeOptions = { branch, project, ...badgeOptions }; return ( <div className="overview-meta-card"> - <button className="js-project-badges" onClick={this.handleOpen}> + <Button className="js-project-badges" onClick={this.handleOpen}> {translate('overview.badges.get_badge')} - </button> + </Button> {this.state.open && ( <Modal contentLabel={header} onRequestClose={this.handleClose}> <header className="modal-head"> @@ -100,9 +104,9 @@ export default class BadgesModal extends React.PureComponent<Props, State> { <BadgeSnippet snippet={getBadgeUrl(selectedType, fullBadgeOptions)} /> </div> <footer className="modal-foot"> - <button className="button-link js-modal-close" onClick={this.handleCancelClick}> + <ResetButtonLink className="js-modal-close" onClick={this.handleClose}> {translate('close')} - </button> + </ResetButtonLink> </footer> </Modal> )} diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgeButton-test.tsx b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgeButton-test.tsx index 3f958698649..4e45f44e94a 100644 --- a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgeButton-test.tsx +++ b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgeButton-test.tsx @@ -32,7 +32,7 @@ it('should display correctly', () => { it('should return the badge type on click', () => { const onClick = jest.fn(); const wrapper = getWrapper({ onClick }); - click(wrapper.find('button')); + click(wrapper.find('Button')); expect(onClick).toHaveBeenCalledWith(BadgeType.marketing); }); diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx index fc403fc3f17..cea857f2e90 100644 --- a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx +++ b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx @@ -29,6 +29,6 @@ jest.mock('../../../../helpers/urls', () => ({ it('should display the modal after click', () => { const wrapper = shallow(<BadgesModal branch="branch-6.6" metrics={{}} project="foo" />); expect(wrapper).toMatchSnapshot(); - click(wrapper.find('button')); + click(wrapper.find('Button')); expect(wrapper.find('Modal')).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgeButton-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgeButton-test.tsx.snap index 90b4d8e5e37..c1ea1a7eb50 100644 --- a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgeButton-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgeButton-test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should display correctly 1`] = ` -<button +<Button className="badge-button" onClick={[Function]} > @@ -10,11 +10,11 @@ exports[`should display correctly 1`] = ` src="http://foo.bar" width="128px" /> -</button> +</Button> `; exports[`should display correctly 2`] = ` -<button +<Button className="badge-button selected" onClick={[Function]} > @@ -23,11 +23,11 @@ exports[`should display correctly 2`] = ` src="http://foo.bar" width="128px" /> -</button> +</Button> `; exports[`should display correctly 3`] = ` -<button +<Button className="badge-button" onClick={[Function]} > @@ -35,5 +35,5 @@ exports[`should display correctly 3`] = ` alt="overview.badges.measure.alt" src="http://foo.bar" /> -</button> +</Button> `; diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgesModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgesModal-test.tsx.snap index e813fd772bd..8366543e432 100644 --- a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgesModal-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgesModal-test.tsx.snap @@ -4,12 +4,12 @@ exports[`should display the modal after click 1`] = ` <div className="overview-meta-card" > - <button + <Button className="js-project-badges" onClick={[Function]} > overview.badges.get_badge - </button> + </Button> </div> `; @@ -82,12 +82,12 @@ exports[`should display the modal after click 2`] = ` <footer className="modal-foot" > - <button - className="button-link js-modal-close" + <ResetButtonLink + className="js-modal-close" onClick={[Function]} > close - </button> + </ResetButtonLink> </footer> </Modal> `; diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaTags.tsx b/server/sonar-web/src/main/js/apps/overview/meta/MetaTags.tsx index 07365b26330..4dde591feb7 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/MetaTags.tsx +++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaTags.tsx @@ -24,6 +24,7 @@ import { translate } from '../../../helpers/l10n'; import TagsList from '../../../components/tags/TagsList'; import { BubblePopupPosition } from '../../../components/common/BubblePopup'; import { Component } from '../../../app/types'; +import { Button } from '../../../components/ui/buttons'; interface Props { component: Component; @@ -37,7 +38,7 @@ interface State { export default class MetaTags extends React.PureComponent<Props, State> { card?: HTMLDivElement | null; - tagsList?: HTMLButtonElement | null; + tagsList?: HTMLElement | null; tagsSelector?: HTMLDivElement | null; state: State = { popupOpen: false, popupPosition: { top: 0, right: 0 } }; @@ -70,8 +71,7 @@ export default class MetaTags extends React.PureComponent<Props, State> { } }; - handleClick = (evt: React.SyntheticEvent<HTMLButtonElement>) => { - evt.stopPropagation(); + handleClick = () => { this.setState(state => ({ popupOpen: !state.popupOpen })); }; @@ -100,12 +100,13 @@ export default class MetaTags extends React.PureComponent<Props, State> { if (this.canUpdateTags()) { return ( <div className="big-spacer-top overview-meta-tags" ref={card => (this.card = card)}> - <button + <Button className="button-link" + innerRef={tagsList => (this.tagsList = tagsList)} onClick={this.handleClick} - ref={tagsList => (this.tagsList = tagsList)}> + stopPropagation={true}> <TagsList allowUpdate={true} tags={tags.length ? tags : [translate('no_tags')]} /> - </button> + </Button> {popupOpen && ( <div ref={tagsSelector => (this.tagsSelector = tagsSelector)}> <MetaTagsSelector diff --git a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaTags-test.tsx b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaTags-test.tsx index 1479225a022..7fa2003a331 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaTags-test.tsx +++ b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaTags-test.tsx @@ -72,10 +72,10 @@ it('should open the tag selector on click', () => { expect(wrapper).toMatchSnapshot(); // open - click(wrapper.find('button')); + click(wrapper.find('Button')); expect(wrapper).toMatchSnapshot(); // close - click(wrapper.find('button')); + click(wrapper.find('Button')); expect(wrapper).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaTags-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaTags-test.tsx.snap index 1eea19831ab..d0b063accf7 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaTags-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaTags-test.tsx.snap @@ -4,9 +4,11 @@ exports[`should open the tag selector on click 1`] = ` <div className="big-spacer-top overview-meta-tags" > - <button + <Button className="button-link" + innerRef={[Function]} onClick={[Function]} + stopPropagation={true} > <TagsList allowUpdate={true} @@ -17,7 +19,7 @@ exports[`should open the tag selector on click 1`] = ` ] } /> - </button> + </Button> </div> `; @@ -25,9 +27,11 @@ exports[`should open the tag selector on click 2`] = ` <div className="big-spacer-top overview-meta-tags" > - <button + <Button className="button-link" + innerRef={[Function]} onClick={[Function]} + stopPropagation={true} > <TagsList allowUpdate={true} @@ -38,7 +42,7 @@ exports[`should open the tag selector on click 2`] = ` ] } /> - </button> + </Button> <div> <MetaTagsSelector position={ @@ -64,9 +68,11 @@ exports[`should open the tag selector on click 3`] = ` <div className="big-spacer-top overview-meta-tags" > - <button + <Button className="button-link" + innerRef={[Function]} onClick={[Function]} + stopPropagation={true} > <TagsList allowUpdate={true} @@ -77,7 +83,7 @@ exports[`should open the tag selector on click 3`] = ` ] } /> - </button> + </Button> </div> `; @@ -85,9 +91,11 @@ exports[`should render with tags and admin rights 1`] = ` <div className="big-spacer-top overview-meta-tags" > - <button + <Button className="button-link" + innerRef={[Function]} onClick={[Function]} + stopPropagation={true} > <TagsList allowUpdate={true} @@ -98,7 +106,7 @@ exports[`should render with tags and admin rights 1`] = ` ] } /> - </button> + </Button> </div> `; diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/Form.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/Form.tsx index 3bf36d12b1d..828fc380d78 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/Form.tsx +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/Form.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import DeferredSpinner from '../../../components/common/DeferredSpinner'; import SimpleModal from '../../../components/controls/SimpleModal'; import { translate } from '../../../helpers/l10n'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; interface Props { confirmButtonText: string; @@ -134,17 +135,15 @@ export default class Form extends React.PureComponent<Props, State> { <footer className="modal-foot"> <DeferredSpinner className="spacer-right" loading={submitting} /> - <button disabled={submitting} id="permission-template-submit" type="submit"> + <SubmitButton disabled={submitting} id="permission-template-submit"> {this.props.confirmButtonText} - </button> - <button - className="button-link" + </SubmitButton> + <ResetButtonLink disabled={submitting} id="permission-template-cancel" - onClick={onCloseClick} - type="reset"> + onClick={onCloseClick}> {translate('cancel')} - </button> + </ResetButtonLink> </footer> </form> )} diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx index 1db801795fe..e6d9b84dc88 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import Form from './Form'; import { createPermissionTemplate } from '../../../api/permissions'; +import { Button } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; interface Props { @@ -50,9 +51,7 @@ export default class Header extends React.PureComponent<Props, State> { this.mounted = false; } - handleCreateClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); + handleCreateClick = () => { this.setState({ createModal: true }); }; @@ -86,9 +85,7 @@ export default class Header extends React.PureComponent<Props, State> { {!this.props.ready && <i className="spinner" />} <div className="page-actions"> - <button onClick={this.handleCreateClick} type="button"> - {translate('create')} - </button> + <Button onClick={this.handleCreateClick}>{translate('create')}</Button> {this.state.createModal && ( <Form diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/ApplyTemplate.tsx b/server/sonar-web/src/main/js/apps/permissions/project/components/ApplyTemplate.tsx index e8550cef79a..94fe7d09868 100644 --- a/server/sonar-web/src/main/js/apps/permissions/project/components/ApplyTemplate.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/project/components/ApplyTemplate.tsx @@ -23,6 +23,7 @@ import { PermissionTemplate } from '../../../../app/types'; import DeferredSpinner from '../../../../components/common/DeferredSpinner'; import SimpleModal from '../../../../components/controls/SimpleModal'; import Select from '../../../../components/controls/Select'; +import { SubmitButton, ResetButtonLink } from '../../../../components/ui/buttons'; import { translateWithParameters, translate } from '../../../../helpers/l10n'; interface Props { @@ -139,13 +140,13 @@ export default class ApplyTemplate extends React.PureComponent<Props, State> { <footer className="modal-foot"> <DeferredSpinner className="spacer-right" loading={submitting} /> {!this.state.done && ( - <button disabled={submitting || !this.state.permissionTemplate} type="submit"> + <SubmitButton disabled={submitting || !this.state.permissionTemplate}> {translate('apply')} - </button> + </SubmitButton> )} - <button className="button-link" onClick={onCloseClick} type="reset"> + <ResetButtonLink onClick={onCloseClick}> {translate(this.state.done ? 'close' : 'cancel')} - </button> + </ResetButtonLink> </footer> </form> )} diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.tsx b/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.tsx index bab1ba6490d..f9017efdefa 100644 --- a/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.tsx @@ -20,6 +20,7 @@ import * as React from 'react'; import ApplyTemplate from './ApplyTemplate'; import { Component } from '../../../../app/types'; +import { Button } from '../../../../components/ui/buttons'; import { translate } from '../../../../helpers/l10n'; interface Props { @@ -44,9 +45,7 @@ export default class PageHeader extends React.PureComponent<Props, State> { this.mounted = false; } - handleApplyTemplate = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); + handleApplyTemplate = () => { this.setState({ applyTemplateModal: true }); }; @@ -79,9 +78,9 @@ export default class PageHeader extends React.PureComponent<Props, State> { {canApplyPermissionTemplate && ( <div className="page-actions"> - <button className="js-apply-template" onClick={this.handleApplyTemplate} type="button"> + <Button className="js-apply-template" onClick={this.handleApplyTemplate}> {translate('projects_role.apply_template')} - </button> + </Button> {this.state.applyTemplateModal && ( <ApplyTemplate diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/Subscription.tsx b/server/sonar-web/src/main/js/apps/portfolio/components/Subscription.tsx index 98282a76287..efd920612cd 100644 --- a/server/sonar-web/src/main/js/apps/portfolio/components/Subscription.tsx +++ b/server/sonar-web/src/main/js/apps/portfolio/components/Subscription.tsx @@ -20,6 +20,7 @@ import * as React from 'react'; import { ReportStatus, subscribe, unsubscribe } from '../../../api/report'; import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { Button } from '../../../components/ui/buttons'; interface Props { component: string; @@ -66,18 +67,14 @@ export default class Subscription extends React.PureComponent<Props, State> { } }; - handleSubscribe = (e: React.SyntheticEvent<HTMLButtonElement>) => { - e.preventDefault(); - e.currentTarget.blur(); + handleSubscribe = () => { this.setState({ loading: true }); subscribe(this.props.component) .then(() => this.handleSubscription(true)) .catch(this.stopLoading); }; - handleUnsubscribe = (e: React.SyntheticEvent<HTMLButtonElement>) => { - e.preventDefault(); - e.currentTarget.blur(); + handleUnsubscribe = () => { this.setState({ loading: true }); unsubscribe(this.props.component) .then(() => this.handleSubscription(false)) @@ -100,7 +97,7 @@ export default class Subscription extends React.PureComponent<Props, State> { {translateWithParameters('report.subscribed', this.getEffectiveFrequencyText())} </div> </div> - <button onClick={this.handleUnsubscribe}>{translate('report.unsubscribe')}</button> + <Button onClick={this.handleUnsubscribe}>{translate('report.unsubscribe')}</Button> {this.renderLoading()} </div> ); @@ -110,9 +107,9 @@ export default class Subscription extends React.PureComponent<Props, State> { <p className="spacer-bottom"> {translateWithParameters('report.unsubscribed', this.getEffectiveFrequencyText())} </p> - <button className="js-report-subscribe" onClick={this.handleSubscribe}> + <Button className="js-report-subscribe" onClick={this.handleSubscribe}> {translate('report.subscribe')} - </button> + </Button> {this.renderLoading()} </div> ); diff --git a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Subscription-test.tsx.snap b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Subscription-test.tsx.snap index 03ba0e5f118..488d122219e 100644 --- a/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Subscription-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/Subscription-test.tsx.snap @@ -24,12 +24,12 @@ exports[`renders when not subscribed 1`] = ` > report.unsubscribed.report.frequency.montly.effective </p> - <button + <Button className="js-report-subscribe" onClick={[Function]} > report.subscribe - </button> + </Button> </div> </div> `; @@ -53,11 +53,11 @@ exports[`renders when subscribed 1`] = ` report.subscribed.report.frequency.montly.effective </div> </div> - <button + <Button onClick={[Function]} > report.unsubscribe - </button> + </Button> </div> </div> `; diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/DeleteBranchModal.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/DeleteBranchModal.tsx index 4760621d29c..127b3781189 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/DeleteBranchModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/DeleteBranchModal.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import { deleteBranch } from '../../../api/branches'; import { Branch } from '../../../app/types'; import Modal from '../../../components/controls/Modal'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; import { translate, translateWithParameters } from '../../../helpers/l10n'; interface Props { @@ -64,11 +65,6 @@ export default class DeleteBranchModal extends React.PureComponent<Props, State> ); }; - handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - render() { const { branch } = this.props; const header = translate('branches.delete'); @@ -84,12 +80,10 @@ export default class DeleteBranchModal extends React.PureComponent<Props, State> </div> <footer className="modal-foot"> {this.state.loading && <i className="spinner spacer-right" />} - <button className="button-red" disabled={this.state.loading} type="submit"> + <SubmitButton className="button-red" disabled={this.state.loading}> {translate('delete')} - </button> - <a href="#" onClick={this.handleCancelClick}> - {translate('cancel')} - </a> + </SubmitButton> + <ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink> </footer> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/RenameBranchModal.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/RenameBranchModal.tsx index 67a7656fe0a..bc78f0395cb 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/RenameBranchModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/RenameBranchModal.tsx @@ -20,8 +20,9 @@ import * as React from 'react'; import { renameBranch } from '../../../api/branches'; import { Branch } from '../../../app/types'; -import { translate } from '../../../helpers/l10n'; import Modal from '../../../components/controls/Modal'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; +import { translate } from '../../../helpers/l10n'; interface Props { branch: Branch; @@ -68,11 +69,6 @@ export default class RenameBranchModal extends React.PureComponent<Props, State> ); }; - handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => { this.setState({ name: event.currentTarget.value }); }; @@ -110,12 +106,8 @@ export default class RenameBranchModal extends React.PureComponent<Props, State> </div> <footer className="modal-foot"> {this.state.loading && <i className="spinner spacer-right" />} - <button disabled={submitDisabled} type="submit"> - {translate('rename')} - </button> - <a href="#" onClick={this.handleCancelClick}> - {translate('cancel')} - </a> + <SubmitButton disabled={submitDisabled}>{translate('rename')}</SubmitButton> + <ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink> </footer> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/SettingForm.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/SettingForm.tsx index e862b2b7f8c..6c3b737dd4c 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/SettingForm.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/SettingForm.tsx @@ -19,6 +19,7 @@ */ import * as React from 'react'; import { SettingValue, setSimpleSettingValue, resetSettingValue } from '../../../api/settings'; +import { Button, SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; import { translate, translateWithParameters } from '../../../helpers/l10n'; interface Props { @@ -75,9 +76,7 @@ export default class SettingForm extends React.PureComponent<Props, State> { this.setState({ value: event.currentTarget.value }); }; - handleResetClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); + handleResetClick = () => { this.setState({ submitting: true }); resetSettingValue(this.props.setting.key, this.props.project, this.props.branch).then( this.props.onChange, @@ -89,11 +88,6 @@ export default class SettingForm extends React.PureComponent<Props, State> { ); }; - handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - render() { const { setting } = this.props; const submitDisabled = this.state.submitting || this.state.value === setting.value; @@ -128,21 +122,17 @@ export default class SettingForm extends React.PureComponent<Props, State> { <footer className="modal-foot"> {!setting.inherited && setting.parentValue && ( - <button + <Button className="pull-left" disabled={this.state.submitting} onClick={this.handleResetClick} type="reset"> {translate('reset_to_default')} - </button> + </Button> )} {this.state.submitting && <i className="spinner spacer-right" />} - <button disabled={submitDisabled} type="submit"> - {translate('save')} - </button> - <a href="#" onClick={this.handleCancelClick}> - {translate('cancel')} - </a> + <SubmitButton disabled={submitDisabled}>{translate('save')}</SubmitButton> + <ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink> </footer> </form> ); diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx index d23beef1c6e..88fd8dffca7 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/DeleteBranchModal-test.tsx @@ -55,7 +55,7 @@ it('cancels', () => { const onClose = jest.fn(); const wrapper = shallowRender(jest.fn(), onClose); - click(wrapper.find('a')); + click(wrapper.find('ResetButtonLink')); return doAsync().then(() => { expect(onClose).toBeCalled(); diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/LongBranchesPattern-test.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/LongBranchesPattern-test.tsx index 4a9221d80e9..0e2a0bf65a7 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/LongBranchesPattern-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/LongBranchesPattern-test.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import LongBranchesPattern from '../LongBranchesPattern'; +import { click } from '../../../../helpers/testUtils'; jest.mock('../../../../api/settings', () => ({ getValues: jest.fn(() => Promise.resolve([])) @@ -42,8 +43,7 @@ it('opens form', () => { const wrapper = shallow(<LongBranchesPattern project="project" />); wrapper.setState({ loading: false, setting: { value: 'release-.*' } }); - wrapper.find('EditButton').prop<Function>('onClick')(); - wrapper.update(); + click(wrapper.find('EditButton')); expect(wrapper.find('LongBranchesPatternForm').exists()).toBeTruthy(); wrapper.find('LongBranchesPatternForm').prop<Function>('onClose')(); diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/RenameBranchModal-test.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/RenameBranchModal-test.tsx index 3d897c3a953..65bb3ca6f29 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/RenameBranchModal-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/RenameBranchModal-test.tsx @@ -57,7 +57,7 @@ it('cancels', () => { const onClose = jest.fn(); const wrapper = shallowRender(jest.fn(), onClose); - click(wrapper.find('a')); + click(wrapper.find('ResetButtonLink')); return doAsync().then(() => { expect(onClose).toBeCalled(); diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/SettingForm-test.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/SettingForm-test.tsx index ed442a4bdd8..6a107645fc0 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/SettingForm-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/SettingForm-test.tsx @@ -75,7 +75,7 @@ it('resets value', async () => { ); expect(wrapper).toMatchSnapshot(); - click(wrapper.find('button[type="reset"]')); + click(wrapper.find('Button')); expect(resetSettingValue).toBeCalledWith('foo', 'project', undefined); await new Promise(setImmediate); diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/DeleteBranchModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/DeleteBranchModal-test.tsx.snap index 8571673e611..3864d0c38fd 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/DeleteBranchModal-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/DeleteBranchModal-test.tsx.snap @@ -23,19 +23,17 @@ exports[`renders 1`] = ` <footer className="modal-foot" > - <button + <SubmitButton className="button-red" disabled={false} - type="submit" > delete - </button> - <a - href="#" - onClick={[Function]} + </SubmitButton> + <ResetButtonLink + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </form> </Modal> @@ -67,19 +65,17 @@ exports[`renders 2`] = ` <i className="spinner spacer-right" /> - <button + <SubmitButton className="button-red" disabled={true} - type="submit" > delete - </button> - <a - href="#" - onClick={[Function]} + </SubmitButton> + <ResetButtonLink + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/RenameBranchModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/RenameBranchModal-test.tsx.snap index 2960f04d2d9..77c4b64b7f1 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/RenameBranchModal-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/RenameBranchModal-test.tsx.snap @@ -47,18 +47,16 @@ exports[`renders 1`] = ` <footer className="modal-foot" > - <button + <SubmitButton disabled={true} - type="submit" > rename - </button> - <a - href="#" - onClick={[Function]} + </SubmitButton> + <ResetButtonLink + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </form> </Modal> @@ -111,18 +109,16 @@ exports[`renders 2`] = ` <footer className="modal-foot" > - <button + <SubmitButton disabled={false} - type="submit" > rename - </button> - <a - href="#" - onClick={[Function]} + </SubmitButton> + <ResetButtonLink + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </form> </Modal> @@ -178,18 +174,16 @@ exports[`renders 3`] = ` <i className="spinner spacer-right" /> - <button + <SubmitButton disabled={true} - type="submit" > rename - </button> - <a - href="#" - onClick={[Function]} + </SubmitButton> + <ResetButtonLink + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/SettingForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/SettingForm-test.tsx.snap index bec3f234357..1c038c720ff 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/SettingForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/SettingForm-test.tsx.snap @@ -36,18 +36,16 @@ exports[`changes value 1`] = ` <footer className="modal-foot" > - <button + <SubmitButton disabled={true} - type="submit" > save - </button> - <a - href="#" - onClick={[Function]} + </SubmitButton> + <ResetButtonLink + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </form> `; @@ -88,26 +86,24 @@ exports[`resets value 1`] = ` <footer className="modal-foot" > - <button + <Button className="pull-left" disabled={false} onClick={[Function]} type="reset" > reset_to_default - </button> - <button + </Button> + <SubmitButton disabled={true} - type="submit" > save - </button> - <a - href="#" - onClick={[Function]} + </SubmitButton> + <ResetButtonLink + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </form> `; diff --git a/server/sonar-web/src/main/js/apps/projects/components/ClearAll.tsx b/server/sonar-web/src/main/js/apps/projects/components/ClearAll.tsx index 0e2772309d4..ddf2825d011 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ClearAll.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/ClearAll.tsx @@ -18,26 +18,19 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { Button } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; interface Props { onClearAll: () => void; } -export default class ClearAll extends React.PureComponent<Props> { - handleClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); - this.props.onClearAll(); - }; - - render() { - return ( - <div className="projects-facets-reset"> - <button className="button-red" onClick={this.handleClick}> - {translate('clear_all_filters')} - </button> - </div> - ); - } +export default function ClearAll({ onClearAll }: Props) { + return ( + <div className="projects-facets-reset"> + <Button className="button-red" onClick={onClearAll}> + {translate('clear_all_filters')} + </Button> + </div> + ); } diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ClearAll-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ClearAll-test.tsx index 6978b60dcda..8ff409b905a 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ClearAll-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ClearAll-test.tsx @@ -29,6 +29,6 @@ it('renders', () => { it('clears all', () => { const onClearAll = jest.fn(); const wrapper = shallow(<ClearAll onClearAll={onClearAll} />); - click(wrapper.find('button')); + click(wrapper.find('Button')); expect(onClearAll).toBeCalled(); }); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelect-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelect-test.tsx index f38303cf520..19ca26cc2a6 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelect-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelect-test.tsx @@ -20,6 +20,7 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import ProjectsSortingSelect from '../ProjectsSortingSelect'; +import { click } from '../../../../helpers/testUtils'; it('should render correctly for overall view', () => { expect( @@ -84,6 +85,6 @@ it('reverses sorting', () => { view="overall" /> ); - wrapper.find('ButtonIcon').prop<Function>('onClick')(); + click(wrapper.find('ButtonIcon')); expect(onChange).toBeCalledWith('size', false); }); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ClearAll-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ClearAll-test.tsx.snap index 5f61d820953..2340e0bbf03 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ClearAll-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ClearAll-test.tsx.snap @@ -4,11 +4,11 @@ exports[`renders 1`] = ` <div className="projects-facets-reset" > - <button + <Button className="button-red" - onClick={[Function]} + onClick={[MockFunction]} > clear_all_filters - </button> + </Button> </div> `; diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx index 69bb55161c7..663c5105577 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx @@ -24,6 +24,7 @@ import { translate, translateWithParameters } from '../../helpers/l10n'; import AlertWarnIcon from '../../components/icons-components/AlertWarnIcon'; import Modal from '../../components/controls/Modal'; import Select from '../../components/controls/Select'; +import { Button, ResetButtonLink } from '../../components/ui/buttons'; export interface Props { analyzedBefore?: string; @@ -78,11 +79,6 @@ export default class BulkApplyTemplateModal extends React.PureComponent<Props, S ); } - handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - handleConfirmClick = () => { const { permissionTemplate } = this.state; if (permissionTemplate) { @@ -180,13 +176,13 @@ export default class BulkApplyTemplateModal extends React.PureComponent<Props, S {!loading && !done && permissionTemplates && ( - <button disabled={submitting} onClick={this.handleConfirmClick}> + <Button disabled={submitting} onClick={this.handleConfirmClick}> {translate('apply')} - </button> + </Button> )} - <a className="js-modal-close" href="#" onClick={this.handleCancelClick}> + <ResetButtonLink className="js-modal-close" onClick={this.props.onClose}> {done ? translate('close') : translate('cancel')} - </a> + </ResetButtonLink> </footer> </Modal> ); diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/ChangeVisibilityForm.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/ChangeVisibilityForm.tsx index 359fc4198c1..d603ab0a5d4 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/ChangeVisibilityForm.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/ChangeVisibilityForm.tsx @@ -21,8 +21,9 @@ import * as React from 'react'; import * as classNames from 'classnames'; import { Organization, Visibility } from '../../app/types'; import UpgradeOrganizationBox from '../../components/common/UpgradeOrganizationBox'; -import { translate } from '../../helpers/l10n'; import Modal from '../../components/controls/Modal'; +import { Button, ResetButtonLink } from '../../components/ui/buttons'; +import { translate } from '../../helpers/l10n'; export interface Props { onClose: () => void; @@ -40,13 +41,7 @@ export default class ChangeVisibilityForm extends React.PureComponent<Props, Sta this.state = { visibility: props.organization.projectVisibility as Visibility }; } - handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - - handleConfirmClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); + handleConfirmClick = () => { this.props.onConfirm(this.state.visibility); this.props.onClose(); }; @@ -111,12 +106,12 @@ export default class ChangeVisibilityForm extends React.PureComponent<Props, Sta </div> <footer className="modal-foot"> - <button className="js-confirm" onClick={this.handleConfirmClick}> + <Button className="js-confirm" onClick={this.handleConfirmClick}> {translate('organization.change_visibility_form.submit')} - </button> - <a className="js-modal-close" href="#" onClick={this.handleCancelClick}> + </Button> + <ResetButtonLink className="js-modal-close" onClick={this.props.onClose}> {translate('cancel')} - </a> + </ResetButtonLink> </footer> </Modal> ); diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx index b43aadab9c7..5a39b28c5f8 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx @@ -20,13 +20,14 @@ import * as React from 'react'; import { Link } from 'react-router'; import { FormattedMessage } from 'react-intl'; +import { createProject } from '../../api/components'; import { Organization } from '../../app/types'; import UpgradeOrganizationBox from '../../components/common/UpgradeOrganizationBox'; import VisibilitySelector from '../../components/common/VisibilitySelector'; -import { createProject } from '../../api/components'; +import Modal from '../../components/controls/Modal'; +import { SubmitButton, ResetButtonLink } from '../../components/ui/buttons'; import { translate } from '../../helpers/l10n'; import { getProjectUrl } from '../../helpers/urls'; -import Modal from '../../components/controls/Modal'; interface Props { onClose: () => void; @@ -79,11 +80,6 @@ export default class CreateProjectForm extends React.PureComponent<Props, State> this.mounted = false; } - handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - handleAdvancedClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { event.preventDefault(); event.currentTarget.blur(); @@ -155,13 +151,12 @@ export default class CreateProjectForm extends React.PureComponent<Props, State> </div> <footer className="modal-foot"> - <a - href="#" + <ResetButtonLink id="create-project-close" - onClick={this.handleCancelClick} - ref={node => (this.closeButton = node)}> + innerRef={node => (this.closeButton = node)} + onClick={this.props.onClose}> {translate('close')} - </a> + </ResetButtonLink> </footer> </div> ) : ( @@ -243,12 +238,12 @@ export default class CreateProjectForm extends React.PureComponent<Props, State> <footer className="modal-foot"> {this.state.loading && <i className="spinner spacer-right" />} - <button disabled={this.state.loading} id="create-project-submit" type="submit"> + <SubmitButton disabled={this.state.loading} id="create-project-submit"> {translate('create')} - </button> - <a href="#" id="create-project-cancel" onClick={this.handleCancelClick}> + </SubmitButton> + <ResetButtonLink id="create-project-cancel" onClick={this.props.onClose}> {translate('cancel')} - </a> + </ResetButtonLink> </footer> </form> )} diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/DeleteModal.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/DeleteModal.tsx index a9b45bb2a52..9300d2bc96b 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/DeleteModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/DeleteModal.tsx @@ -19,9 +19,10 @@ */ import * as React from 'react'; import { bulkDeleteProjects } from '../../api/components'; -import { translate, translateWithParameters } from '../../helpers/l10n'; -import AlertWarnIcon from '../../components/icons-components/AlertWarnIcon'; import Modal from '../../components/controls/Modal'; +import AlertWarnIcon from '../../components/icons-components/AlertWarnIcon'; +import { Button, ResetButtonLink } from '../../components/ui/buttons'; +import { translate, translateWithParameters } from '../../helpers/l10n'; export interface Props { analyzedBefore?: string; @@ -51,11 +52,6 @@ export default class DeleteModal extends React.PureComponent<Props, State> { this.mounted = false; } - handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - handleConfirmClick = () => { this.setState({ loading: true }); const parameters = this.props.selection.length @@ -112,15 +108,15 @@ export default class DeleteModal extends React.PureComponent<Props, State> { <footer className="modal-foot"> {this.state.loading && <i className="spinner spacer-right" />} - <button + <Button className="button-red" disabled={this.state.loading} onClick={this.handleConfirmClick}> {translate('delete')} - </button> - <a className="js-modal-close" href="#" onClick={this.handleCancelClick}> + </Button> + <ResetButtonLink className="js-modal-close" onClick={this.props.onClose}> {translate('cancel')} - </a> + </ResetButtonLink> </footer> </Modal> ); diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx index 3b2b14d39b1..90015c3cf5d 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx @@ -20,8 +20,8 @@ import * as React from 'react'; import ChangeVisibilityForm from './ChangeVisibilityForm'; import { Organization, Visibility } from '../../app/types'; +import { EditButton, Button } from '../../components/ui/buttons'; import { translate } from '../../helpers/l10n'; -import { EditButton } from '../../components/ui/buttons'; export interface Props { hasProvisionPermission?: boolean; @@ -37,11 +37,6 @@ interface State { export default class Header extends React.PureComponent<Props, State> { state: State = { visibilityForm: false }; - handleCreateProjectClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - this.props.onProjectCreate(); - }; - handleChangeVisibilityClick = () => { this.setState({ visibilityForm: true }); }; @@ -69,9 +64,9 @@ export default class Header extends React.PureComponent<Props, State> { /> </span> {this.props.hasProvisionPermission && ( - <button id="create-project" onClick={this.handleCreateProjectClick}> + <Button id="create-project" onClick={this.props.onProjectCreate}> {translate('qualifiers.create.TRK')} - </button> + </Button> )} </div> diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/RestoreAccessModal.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/RestoreAccessModal.tsx index d936fafa1e9..57183554451 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/RestoreAccessModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/RestoreAccessModal.tsx @@ -22,6 +22,7 @@ import { FormattedMessage } from 'react-intl'; import { Project } from './utils'; import { grantPermissionToUser } from '../../api/permissions'; import Modal from '../../components/controls/Modal'; +import { SubmitButton, ResetButtonLink } from '../../components/ui/buttons'; import { translate } from '../../helpers/l10n'; interface Props { @@ -47,11 +48,6 @@ export default class RestoreAccessModal extends React.PureComponent<Props, State this.mounted = false; } - handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { event.preventDefault(); this.setState({ loading: true }); @@ -96,12 +92,8 @@ export default class RestoreAccessModal extends React.PureComponent<Props, State <footer className="modal-foot"> {this.state.loading && <i className="spinner spacer-right" />} - <button disabled={this.state.loading} type="submit"> - {translate('restore')} - </button> - <a className="js-modal-close" href="#" onClick={this.handleCancelClick}> - {translate('cancel')} - </a> + <SubmitButton disabled={this.state.loading}>{translate('restore')}</SubmitButton> + <ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink> </footer> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx index 6e3fa3d02ac..c2df72d6bef 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx @@ -30,6 +30,7 @@ import Tooltip from '../../components/controls/Tooltip'; import DateInput from '../../components/controls/DateInput'; import Select from '../../components/controls/Select'; import SearchBox from '../../components/controls/SearchBox'; +import { Button } from '../../components/ui/buttons'; export interface Props { analyzedBefore?: string; @@ -76,9 +77,7 @@ export default class Search extends React.PureComponent<Props, State> { } }; - handleDeleteClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); + handleDeleteClick = () => { this.setState({ deleteModal: true }); }; @@ -91,9 +90,7 @@ export default class Search extends React.PureComponent<Props, State> { this.props.onDeleteProjects(); }; - handleBulkApplyTemplateClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); + handleBulkApplyTemplateClick = () => { this.setState({ bulkApplyTemplateModal: true }); }; @@ -115,8 +112,8 @@ export default class Search extends React.PureComponent<Props, State> { <Checkbox checked={checked} id="projects-selection" - thirdState={thirdState} onCheck={this.onCheck} + thirdState={thirdState} /> ); }; @@ -139,13 +136,13 @@ export default class Search extends React.PureComponent<Props, State> { className="input-medium" clearable={false} disabled={!this.props.ready} + name="projects-qualifier" + onChange={this.handleQualifierChange} optionRenderer={this.renderQualifierOption} options={this.getQualifierOptions()} + searchable={false} value={this.props.qualifiers} valueRenderer={this.renderQualifierOption} - name="projects-qualifier" - onChange={this.handleQualifierChange} - searchable={false} /> </td> ); @@ -155,8 +152,8 @@ export default class Search extends React.PureComponent<Props, State> { this.props.qualifiers === 'TRK' ? ( <td className="thin nowrap text-middle"> <Checkbox - className="link-checkbox-control" checked={this.props.provisioned} + className="link-checkbox-control" id="projects-provisioned" onCheck={this.props.onProvisionedChanged}> <span className="little-spacer-left"> @@ -204,19 +201,19 @@ export default class Search extends React.PureComponent<Props, State> { /> </td> <td className="thin nowrap text-middle"> - <button + <Button className="js-bulk-apply-permission-template" disabled={this.props.total === 0} onClick={this.handleBulkApplyTemplateClick}> {translate('permission_templates.bulk_apply_permission_template')} - </button> + </Button> {this.props.qualifiers === 'TRK' && ( - <button + <Button className="js-delete spacer-left button-red" disabled={this.props.total === 0} onClick={this.handleDeleteClick}> {translate('delete')} - </button> + </Button> )} </td> </tr> diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx index 09c56289581..2ce76067896 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx @@ -56,7 +56,7 @@ it('bulk applies template to all results', async () => { }); expect(wrapper).toMatchSnapshot(); - click(wrapper.find('button')); + click(wrapper.find('Button')); expect(bulkApplyTemplate).toBeCalledWith({ analyzedBefore: '2017-04-08T00:00:00.000Z', onProvisionedOnly: true, @@ -83,7 +83,7 @@ it('bulk applies template to selected results', async () => { }); expect(wrapper).toMatchSnapshot(); - click(wrapper.find('button')); + click(wrapper.find('Button')); expect(wrapper).toMatchSnapshot(); await new Promise(setImmediate); expect(bulkApplyTemplate).toBeCalledWith({ diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/DeleteModal-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/DeleteModal-test.tsx index 2aaa50a288f..853593d2058 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/DeleteModal-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/DeleteModal-test.tsx @@ -39,7 +39,7 @@ it('deletes all projects', async () => { (wrapper.instance() as DeleteModal).mounted = true; expect(wrapper).toMatchSnapshot(); - click(wrapper.find('button')); + click(wrapper.find('Button')); expect(wrapper).toMatchSnapshot(); expect(bulkDeleteProjects).toBeCalledWith({ analyzedBefore: '2017-04-08T00:00:00.000Z', @@ -59,7 +59,7 @@ it('deletes selected projects', async () => { (wrapper.instance() as DeleteModal).mounted = true; expect(wrapper).toMatchSnapshot(); - click(wrapper.find('button')); + click(wrapper.find('Button')); expect(wrapper).toMatchSnapshot(); expect(bulkDeleteProjects).toBeCalledWith({ organization: 'org', projects: 'proj1,proj2' }); diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/BulkApplyTemplateModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/BulkApplyTemplateModal-test.tsx.snap index fc6070810f9..2b7b17690f2 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/BulkApplyTemplateModal-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/BulkApplyTemplateModal-test.tsx.snap @@ -22,13 +22,12 @@ exports[`bulk applies template to all results 1`] = ` <footer className="modal-foot" > - <a + <ResetButtonLink className="js-modal-close" - href="#" - onClick={[Function]} + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </Modal> `; @@ -91,19 +90,18 @@ exports[`bulk applies template to all results 2`] = ` <footer className="modal-foot" > - <button + <Button disabled={false} onClick={[Function]} > apply - </button> - <a + </Button> + <ResetButtonLink className="js-modal-close" - href="#" - onClick={[Function]} + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </Modal> `; @@ -169,19 +167,18 @@ exports[`bulk applies template to all results 3`] = ` <i className="spinner spacer-right" /> - <button + <Button disabled={true} onClick={[Function]} > apply - </button> - <a + </Button> + <ResetButtonLink className="js-modal-close" - href="#" - onClick={[Function]} + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </Modal> `; @@ -210,13 +207,12 @@ exports[`bulk applies template to all results 4`] = ` <footer className="modal-foot" > - <a + <ResetButtonLink className="js-modal-close" - href="#" - onClick={[Function]} + onClick={[MockFunction]} > close - </a> + </ResetButtonLink> </footer> </Modal> `; @@ -243,13 +239,12 @@ exports[`bulk applies template to selected results 1`] = ` <footer className="modal-foot" > - <a + <ResetButtonLink className="js-modal-close" - href="#" - onClick={[Function]} + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </Modal> `; @@ -312,19 +307,18 @@ exports[`bulk applies template to selected results 2`] = ` <footer className="modal-foot" > - <button + <Button disabled={false} onClick={[Function]} > apply - </button> - <a + </Button> + <ResetButtonLink className="js-modal-close" - href="#" - onClick={[Function]} + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </Modal> `; @@ -390,19 +384,18 @@ exports[`bulk applies template to selected results 3`] = ` <i className="spinner spacer-right" /> - <button + <Button disabled={true} onClick={[Function]} > apply - </button> - <a + </Button> + <ResetButtonLink className="js-modal-close" - href="#" - onClick={[Function]} + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </Modal> `; @@ -431,13 +424,12 @@ exports[`bulk applies template to selected results 4`] = ` <footer className="modal-foot" > - <a + <ResetButtonLink className="js-modal-close" - href="#" - onClick={[Function]} + onClick={[MockFunction]} > close - </a> + </ResetButtonLink> </footer> </Modal> `; diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ChangeVisibilityForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ChangeVisibilityForm-test.tsx.snap index c2471bfc944..0375c6c07e8 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ChangeVisibilityForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ChangeVisibilityForm-test.tsx.snap @@ -80,19 +80,18 @@ exports[`changes visibility 1`] = ` <footer className="modal-foot" > - <button + <Button className="js-confirm" onClick={[Function]} > organization.change_visibility_form.submit - </button> - <a + </Button> + <ResetButtonLink className="js-modal-close" - href="#" - onClick={[Function]} + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </Modal> `; @@ -177,19 +176,18 @@ exports[`changes visibility 2`] = ` <footer className="modal-foot" > - <button + <Button className="js-confirm" onClick={[Function]} > organization.change_visibility_form.submit - </button> - <a + </Button> + <ResetButtonLink className="js-modal-close" - href="#" - onClick={[Function]} + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </Modal> `; @@ -269,19 +267,18 @@ exports[`renders disabled 1`] = ` <footer className="modal-foot" > - <button + <Button className="js-confirm" onClick={[Function]} > organization.change_visibility_form.submit - </button> - <a + </Button> + <ResetButtonLink className="js-modal-close" - href="#" - onClick={[Function]} + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </Modal> `; diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap index 71af5dfa82d..deec1e2e7fd 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap @@ -100,20 +100,18 @@ exports[`creates project 1`] = ` <footer className="modal-foot" > - <button + <SubmitButton disabled={false} id="create-project-submit" - type="submit" > create - </button> - <a - href="#" + </SubmitButton> + <ResetButtonLink id="create-project-cancel" - onClick={[Function]} + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </form> </Modal> @@ -219,20 +217,18 @@ exports[`creates project 2`] = ` <footer className="modal-foot" > - <button + <SubmitButton disabled={false} id="create-project-submit" - type="submit" > create - </button> - <a - href="#" + </SubmitButton> + <ResetButtonLink id="create-project-cancel" - onClick={[Function]} + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </form> </Modal> @@ -341,20 +337,18 @@ exports[`creates project 3`] = ` <i className="spinner spacer-right" /> - <button + <SubmitButton disabled={true} id="create-project-submit" - type="submit" > create - </button> - <a - href="#" + </SubmitButton> + <ResetButtonLink id="create-project-cancel" - onClick={[Function]} + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </form> </Modal> @@ -407,13 +401,13 @@ exports[`creates project 4`] = ` <footer className="modal-foot" > - <a - href="#" + <ResetButtonLink id="create-project-close" - onClick={[Function]} + innerRef={[Function]} + onClick={[MockFunction]} > close - </a> + </ResetButtonLink> </footer> </div> </Modal> @@ -519,20 +513,18 @@ exports[`shows more 1`] = ` <footer className="modal-foot" > - <button + <SubmitButton disabled={false} id="create-project-submit" - type="submit" > create - </button> - <a - href="#" + </SubmitButton> + <ResetButtonLink id="create-project-cancel" - onClick={[Function]} + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </form> </Modal> @@ -645,20 +637,18 @@ exports[`shows more 2`] = ` <footer className="modal-foot" > - <button + <SubmitButton disabled={false} id="create-project-submit" - type="submit" > create - </button> - <a - href="#" + </SubmitButton> + <ResetButtonLink id="create-project-cancel" - onClick={[Function]} + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/DeleteModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/DeleteModal-test.tsx.snap index 77ea7ac64fc..3fed0f28acd 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/DeleteModal-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/DeleteModal-test.tsx.snap @@ -28,20 +28,19 @@ exports[`deletes all projects 1`] = ` <footer className="modal-foot" > - <button + <Button className="button-red" disabled={false} onClick={[Function]} > delete - </button> - <a + </Button> + <ResetButtonLink className="js-modal-close" - href="#" - onClick={[Function]} + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </Modal> `; @@ -77,20 +76,19 @@ exports[`deletes all projects 2`] = ` <i className="spinner spacer-right" /> - <button + <Button className="button-red" disabled={true} onClick={[Function]} > delete - </button> - <a + </Button> + <ResetButtonLink className="js-modal-close" - href="#" - onClick={[Function]} + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </Modal> `; @@ -123,20 +121,19 @@ exports[`deletes selected projects 1`] = ` <footer className="modal-foot" > - <button + <Button className="button-red" disabled={false} onClick={[Function]} > delete - </button> - <a + </Button> + <ResetButtonLink className="js-modal-close" - href="#" - onClick={[Function]} + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </Modal> `; @@ -172,20 +169,19 @@ exports[`deletes selected projects 2`] = ` <i className="spinner spacer-right" /> - <button + <Button className="button-red" disabled={true} onClick={[Function]} > delete - </button> - <a + </Button> + <ResetButtonLink className="js-modal-close" - href="#" - onClick={[Function]} + onClick={[MockFunction]} > cancel - </a> + </ResetButtonLink> </footer> </Modal> `; diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Header-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Header-test.tsx.snap index aefc34315dd..456011bf7c6 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Header-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Header-test.tsx.snap @@ -43,12 +43,12 @@ exports[`renders 1`] = ` onClick={[Function]} /> </span> - <button + <Button id="create-project" - onClick={[Function]} + onClick={[MockFunction]} > qualifiers.create.TRK - </button> + </Button> </div> <p className="page-description" diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Search-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Search-test.tsx.snap index 15ccbea3e9b..31530ef6a9d 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Search-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Search-test.tsx.snap @@ -130,20 +130,20 @@ exports[`render qualifiers filter 1`] = ` <td className="thin nowrap text-middle" > - <button + <Button className="js-bulk-apply-permission-template" disabled={false} onClick={[Function]} > permission_templates.bulk_apply_permission_template - </button> - <button + </Button> + <Button className="js-delete spacer-left button-red" disabled={false} onClick={[Function]} > delete - </button> + </Button> </td> </tr> </tbody> @@ -220,20 +220,20 @@ exports[`renders 1`] = ` <td className="thin nowrap text-middle" > - <button + <Button className="js-bulk-apply-permission-template" disabled={false} onClick={[Function]} > permission_templates.bulk_apply_permission_template - </button> - <button + </Button> + <Button className="js-delete spacer-left button-red" disabled={false} onClick={[Function]} > delete - </button> + </Button> </td> </tr> </tbody> 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 c5e3c2be471..2fd57fb40b8 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 @@ -20,8 +20,6 @@ import * as React from 'react'; import DeleteConditionForm from './DeleteConditionForm'; import ThresholdInput from './ThresholdInput'; -import Checkbox from '../../../components/controls/Checkbox'; -import Select from '../../../components/controls/Select'; import { Condition as ICondition, ConditionBase, @@ -30,6 +28,9 @@ import { updateCondition } from '../../../api/quality-gates'; import { Metric } 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'; @@ -133,14 +134,17 @@ export default class Condition extends React.PureComponent<Props, State> { this.props.onResetError(); }; - handleCancelClick = (e: React.SyntheticEvent<HTMLAnchorElement>) => { - e.preventDefault(); - e.stopPropagation(); + handleCancelClick = () => { this.props.onDeleteCondition(this.props.condition); }; - openDeleteConditionForm = () => this.setState({ openDeleteCondition: true }); - closeDeleteConditionForm = () => this.setState({ openDeleteCondition: false }); + openDeleteConditionForm = () => { + this.setState({ openDeleteCondition: true }); + }; + + closeDeleteConditionForm = () => { + this.setState({ openDeleteCondition: false }); + }; renderPeriodValue() { const { condition, metric } = this.props; @@ -228,10 +232,10 @@ export default class Condition extends React.PureComponent<Props, State> { <td className="thin text-middle nowrap"> {edit ? ( <ThresholdInput - name="warning" - value={this.state.warning} metric={metric} + name="warning" onChange={this.handleWarningChange} + value={this.state.warning} /> ) : ( formatMeasure(condition.warning, metric.type) @@ -241,10 +245,10 @@ export default class Condition extends React.PureComponent<Props, State> { <td className="thin text-middle nowrap"> {edit ? ( <ThresholdInput - name="error" - value={this.state.error} metric={metric} + name="error" onChange={this.handleErrorChange} + value={this.state.error} /> ) : ( formatMeasure(condition.error, metric.type) @@ -255,17 +259,17 @@ export default class Condition extends React.PureComponent<Props, State> { <td className="thin text-middle nowrap"> {condition.id ? ( <div> - <button + <Button className="update-condition" disabled={!this.state.changed} onClick={this.handleUpdateClick}> {translate('update_verb')} - </button> - <button + </Button> + <Button className="button-red delete-condition little-spacer-left" onClick={this.openDeleteConditionForm}> {translate('delete')} - </button> + </Button> {this.state.openDeleteCondition && ( <DeleteConditionForm condition={condition} @@ -278,15 +282,14 @@ export default class Condition extends React.PureComponent<Props, State> { </div> ) : ( <div> - <button className="add-condition" onClick={this.handleSaveClick}> + <Button className="add-condition" onClick={this.handleSaveClick}> {translate('add_verb')} - </button> - <a + </Button> + <ResetButtonLink className="cancel-add-condition spacer-left" - href="#" onClick={this.handleCancelClick}> {translate('cancel')} - </a> + </ResetButtonLink> </div> )} </td> diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx index 4c16c04184c..2c0609e5f66 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx @@ -19,10 +19,11 @@ */ import * as React from 'react'; import * as PropTypes from 'prop-types'; -import Modal from '../../../components/controls/Modal'; import { copyQualityGate, QualityGate } from '../../../api/quality-gates'; -import { getQualityGateUrl } from '../../../helpers/urls'; +import Modal from '../../../components/controls/Modal'; +import { Button, ResetButtonLink } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; +import { getQualityGateUrl } from '../../../helpers/urls'; interface Props { qualityGate: QualityGate; @@ -56,11 +57,6 @@ export default class CopyQualityGateForm extends React.PureComponent<Props, Stat this.mounted = false; } - handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => { this.setState({ name: event.currentTarget.value }); }; @@ -120,12 +116,12 @@ export default class CopyQualityGateForm extends React.PureComponent<Props, Stat </div> <div className="modal-foot"> {loading && <i className="spinner spacer-right" />} - <button disabled={submitDisabled} className="js-confirm"> + <Button className="js-confirm" disabled={submitDisabled}> {translate('copy')} - </button> - <a href="#" className="js-modal-close" onClick={this.handleCancelClick}> + </Button> + <ResetButtonLink className="js-modal-close" onClick={this.props.onClose}> {translate('cancel')} - </a> + </ResetButtonLink> </div> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/CreateQualityGateForm.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/CreateQualityGateForm.tsx index cb228fd06ad..f34aad8bfb9 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/CreateQualityGateForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/CreateQualityGateForm.tsx @@ -19,8 +19,9 @@ */ import * as React from 'react'; import * as PropTypes from 'prop-types'; -import Modal from '../../../components/controls/Modal'; import { createQualityGate, QualityGate } from '../../../api/quality-gates'; +import Modal from '../../../components/controls/Modal'; +import { Button, ResetButtonLink } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; import { getQualityGateUrl } from '../../../helpers/urls'; @@ -52,11 +53,6 @@ export default class CreateQualityGateForm extends React.PureComponent<Props, St this.mounted = false; } - handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => { this.setState({ name: event.currentTarget.value }); }; @@ -113,12 +109,12 @@ export default class CreateQualityGateForm extends React.PureComponent<Props, St </div> <div className="modal-foot"> {loading && <i className="spinner spacer-right" />} - <button disabled={submitDisabled} className="js-confirm"> + <Button className="js-confirm" disabled={submitDisabled}> {translate('save')} - </button> - <a href="#" className="js-modal-close" onClick={this.handleCancelClick}> + </Button> + <ResetButtonLink className="js-modal-close" onClick={this.props.onClose}> {translate('cancel')} - </a> + </ResetButtonLink> </div> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteConditionForm.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteConditionForm.tsx index c8f041fef39..ce41277c1ad 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteConditionForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteConditionForm.tsx @@ -18,9 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import Modal from '../../../components/controls/Modal'; -import { Metric } from '../../../app/types'; 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 { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n'; interface Props { @@ -47,11 +48,6 @@ export default class DeleteConditionForm extends React.PureComponent<Props, Stat this.mounted = false; } - handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { event.preventDefault(); const { organization, condition } = this.props; @@ -86,12 +82,12 @@ export default class DeleteConditionForm extends React.PureComponent<Props, Stat </div> <div className="modal-foot"> {this.state.loading && <i className="spinner spacer-right" />} - <button className="js-delete button-red" disabled={this.state.loading}> + <SubmitButton className="js-delete button-red" disabled={this.state.loading}> {translate('delete')} - </button> - <a href="#" className="js-modal-close" onClick={this.handleCancelClick}> + </SubmitButton> + <ResetButtonLink className="js-modal-close" onClick={this.props.onClose}> {translate('cancel')} - </a> + </ResetButtonLink> </div> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx index b37e21d0b5d..1d0edec416a 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx @@ -19,10 +19,11 @@ */ import * as React from 'react'; import * as PropTypes from 'prop-types'; -import Modal from '../../../components/controls/Modal'; -import { getQualityGatesUrl } from '../../../helpers/urls'; import { deleteQualityGate, QualityGate } from '../../../api/quality-gates'; +import Modal from '../../../components/controls/Modal'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { getQualityGatesUrl } from '../../../helpers/urls'; interface Props { onClose: () => void; @@ -52,11 +53,6 @@ export default class DeleteQualityGateForm extends React.PureComponent<Props, St this.mounted = false; } - handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { event.preventDefault(); const { organization, qualityGate } = this.props; @@ -91,12 +87,12 @@ export default class DeleteQualityGateForm extends React.PureComponent<Props, St </div> <div className="modal-foot"> {this.state.loading && <i className="spinner spacer-right" />} - <button className="js-delete button-red" disabled={this.state.loading}> + <SubmitButton className="js-delete button-red" disabled={this.state.loading}> {translate('delete')} - </button> - <a href="#" className="js-modal-close" onClick={this.handleCancelClick}> + </SubmitButton> + <ResetButtonLink className="js-modal-close" onClick={this.props.onClose}> {translate('cancel')} - </a> + </ResetButtonLink> </div> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx index 244b2a7090b..a661becfdef 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx @@ -23,6 +23,7 @@ import RenameQualityGateForm from './RenameQualityGateForm'; import CopyQualityGateForm from './CopyQualityGateForm'; import DeleteQualityGateForm from './DeleteQualityGateForm'; import { fetchQualityGate, QualityGate, setQualityGateAsDefault } from '../../../api/quality-gates'; +import { Button } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; interface Props { @@ -41,18 +42,15 @@ interface State { export default class DetailsHeader extends React.PureComponent<Props, State> { state = { openPopup: undefined }; - handleRenameClick = (e: React.SyntheticEvent<HTMLButtonElement>) => { - e.preventDefault(); + handleRenameClick = () => { this.setState({ openPopup: 'rename' }); }; - handleCopyClick = (e: React.SyntheticEvent<HTMLButtonElement>) => { - e.preventDefault(); + handleCopyClick = () => { this.setState({ openPopup: 'copy' }); }; - handleSetAsDefaultClick = (e: React.SyntheticEvent<HTMLButtonElement>) => { - e.preventDefault(); + handleSetAsDefaultClick = () => { const { qualityGate, onSetAsDefault, organization } = this.props; if (!qualityGate.isDefault) { setQualityGateAsDefault({ id: qualityGate.id, organization }) @@ -61,8 +59,7 @@ export default class DetailsHeader extends React.PureComponent<Props, State> { } }; - handleDeleteClick = (e: React.SyntheticEvent<HTMLButtonElement>) => { - e.preventDefault(); + handleDeleteClick = () => { this.setState({ openPopup: 'delete' }); }; @@ -83,57 +80,57 @@ export default class DetailsHeader extends React.PureComponent<Props, State> { <div className="pull-right"> {actions.rename && ( - <button id="quality-gate-rename" onClick={this.handleRenameClick}> + <Button id="quality-gate-rename" onClick={this.handleRenameClick}> {translate('rename')} - </button> + </Button> )} {actions.copy && ( - <button + <Button className="little-spacer-left" id="quality-gate-copy" onClick={this.handleCopyClick}> {translate('copy')} - </button> + </Button> )} {actions.setAsDefault && ( - <button + <Button className="little-spacer-left" id="quality-gate-toggle-default" onClick={this.handleSetAsDefaultClick}> {translate('set_as_default')} - </button> + </Button> )} {actions.delete && ( - <button - id="quality-gate-delete" + <Button className="little-spacer-left button-red" + id="quality-gate-delete" onClick={this.handleDeleteClick}> {translate('delete')} - </button> + </Button> )} {openPopup === 'rename' && ( <RenameQualityGateForm + onClose={this.handleClosePopup} onRename={this.props.onRename} organization={organization} - onClose={this.handleClosePopup} qualityGate={qualityGate} /> )} {openPopup === 'copy' && ( <CopyQualityGateForm + onClose={this.handleClosePopup} onCopy={this.props.onCopy} organization={organization} - onClose={this.handleClosePopup} qualityGate={qualityGate} /> )} {openPopup === 'delete' && ( <DeleteQualityGateForm + onClose={this.handleClosePopup} onDelete={this.props.onDelete} organization={organization} - onClose={this.handleClosePopup} qualityGate={qualityGate} /> )} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx index efef33d5335..4a267e581fb 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx @@ -20,8 +20,9 @@ import * as React from 'react'; import CreateQualityGateForm from '../components/CreateQualityGateForm'; import { QualityGate } from '../../../api/quality-gates'; -import { translate } from '../../../helpers/l10n'; import { Organization } from '../../../app/types'; +import { Button } from '../../../components/ui/buttons'; +import { translate } from '../../../helpers/l10n'; interface Props { canCreate: boolean; @@ -36,8 +37,13 @@ interface State { export default class ListHeader extends React.PureComponent<Props, State> { state = { createQualityGateOpen: false }; - openCreateQualityGateForm = () => this.setState({ createQualityGateOpen: true }); - closeCreateQualityGateForm = () => this.setState({ createQualityGateOpen: false }); + openCreateQualityGateForm = () => { + this.setState({ createQualityGateOpen: true }); + }; + + closeCreateQualityGateForm = () => { + this.setState({ createQualityGateOpen: false }); + }; render() { const { organization } = this.props; @@ -47,9 +53,9 @@ export default class ListHeader extends React.PureComponent<Props, State> { <h1 className="page-title">{translate('quality_gates.page')}</h1> {this.props.canCreate && ( <div className="page-actions"> - <button id="quality-gate-add" onClick={this.openCreateQualityGateForm}> + <Button id="quality-gate-add" onClick={this.openCreateQualityGateForm}> {translate('create')} - </button> + </Button> </div> )} {this.state.createQualityGateOpen && ( diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/RenameQualityGateForm.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/RenameQualityGateForm.tsx index 964f160ad40..aebd61dda73 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/RenameQualityGateForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/RenameQualityGateForm.tsx @@ -18,8 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import Modal from '../../../components/controls/Modal'; import { QualityGate, renameQualityGate } from '../../../api/quality-gates'; +import Modal from '../../../components/controls/Modal'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; interface Props { @@ -50,11 +51,6 @@ export default class RenameQualityGateForm extends React.PureComponent<Props, St this.mounted = false; } - handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => { this.setState({ name: event.currentTarget.value }); }; @@ -111,12 +107,12 @@ export default class RenameQualityGateForm extends React.PureComponent<Props, St </div> <div className="modal-foot"> {loading && <i className="spinner spacer-right" />} - <button disabled={submitDisabled} className="js-confirm"> + <SubmitButton className="js-confirm" disabled={submitDisabled}> {translate('rename')} - </button> - <a href="#" className="js-modal-close" onClick={this.handleCancelClick}> + </SubmitButton> + <ResetButtonLink className="js-modal-close" onClick={this.props.onClose}> {translate('cancel')} - </a> + </ResetButtonLink> </div> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.tsx index e4e9220ba8d..f298e8f4056 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.tsx @@ -19,6 +19,7 @@ */ import * as React from 'react'; import DateInput from '../../../components/controls/DateInput'; +import { Button } from '../../../components/ui/buttons'; import { toShortNotSoISOString } from '../../../helpers/dates'; import { translate } from '../../../helpers/l10n'; @@ -31,12 +32,6 @@ interface Props { } export default class ChangelogSearch extends React.PureComponent<Props> { - handleResetClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); - event.currentTarget.blur(); - this.props.onReset(); - }; - formatDate = (date?: string) => (date ? toShortNotSoISOString(date) : undefined); render() { @@ -57,9 +52,9 @@ export default class ChangelogSearch extends React.PureComponent<Props> { placeholder={translate('to')} value={this.formatDate(this.props.toDate)} /> - <button className="spacer-left" onClick={this.handleResetClick}> + <Button className="spacer-left" onClick={this.props.onReset}> {translate('reset_verb')} - </button> + </Button> </div> ); } diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangelogSearch-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangelogSearch-test.tsx index 454bc713ba6..e0812fa388d 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangelogSearch-test.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangelogSearch-test.tsx @@ -29,10 +29,10 @@ it('should render DateInput', () => { const output = shallow( <ChangelogSearch fromDate="2016-01-01" - toDate="2016-05-05" onFromDateChange={onFromDateChange} - onToDateChange={onToDateChange} onReset={jest.fn()} + onToDateChange={onToDateChange} + toDate="2016-05-05" /> ); const dateInputs = output.find(DateInput); @@ -48,13 +48,13 @@ it('should reset', () => { const output = shallow( <ChangelogSearch fromDate="2016-01-01" - toDate="2016-05-05" onFromDateChange={jest.fn()} - onToDateChange={jest.fn()} onReset={onReset} + onToDateChange={jest.fn()} + toDate="2016-05-05" /> ); expect(onReset).not.toBeCalled(); - click(output.find('button')); + click(output.find('Button')); expect(onReset).toBeCalled(); }); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/CopyProfileForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/CopyProfileForm.tsx index 7d04a1f3809..39cd96b4c88 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/components/CopyProfileForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/CopyProfileForm.tsx @@ -18,10 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { Profile } from '../types'; import { copyProfile } from '../../../api/quality-profiles'; import Modal from '../../../components/controls/Modal'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { Profile } from '../types'; interface Props { onClose: () => void; @@ -47,11 +48,6 @@ export default class CopyProfileForm extends React.PureComponent<Props, State> { this.mounted = false; } - handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => { this.setState({ name: event.currentTarget.value }); }; @@ -112,12 +108,12 @@ export default class CopyProfileForm extends React.PureComponent<Props, State> { </div> <div className="modal-foot"> {this.state.loading && <i className="spinner spacer-right" />} - <button disabled={submitDisabled} id="copy-profile-submit"> + <SubmitButton disabled={submitDisabled} id="copy-profile-submit"> {translate('copy')} - </button> - <a href="#" id="copy-profile-cancel" onClick={this.handleCancelClick}> + </SubmitButton> + <ResetButtonLink id="copy-profile-cancel" onClick={this.props.onClose}> {translate('cancel')} - </a> + </ResetButtonLink> </div> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.tsx index 1b564ec9be0..39260d39eb7 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.tsx @@ -18,10 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { Profile } from '../types'; import { deleteProfile } from '../../../api/quality-profiles'; import Modal from '../../../components/controls/Modal'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { Profile } from '../types'; interface Props { onClose: () => void; @@ -47,11 +48,6 @@ export default class DeleteProfileForm extends React.PureComponent<Props, State> this.mounted = false; } - handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { event.preventDefault(); this.setState({ loading: true }); @@ -100,12 +96,15 @@ export default class DeleteProfileForm extends React.PureComponent<Props, State> </div> <div className="modal-foot"> {this.state.loading && <i className="spinner spacer-right" />} - <button className="button-red" disabled={this.state.loading} id="delete-profile-submit"> + <SubmitButton + className="button-red" + disabled={this.state.loading} + id="delete-profile-submit"> {translate('delete')} - </button> - <a href="#" id="delete-profile-cancel" onClick={this.handleCancelClick}> + </SubmitButton> + <ResetButtonLink id="delete-profile-cancel" onClick={this.props.onClose}> {translate('cancel')} - </a> + </ResetButtonLink> </div> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/RenameProfileForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/RenameProfileForm.tsx index 34b610bc7d8..fd04227ede9 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/components/RenameProfileForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/RenameProfileForm.tsx @@ -18,10 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { Profile } from '../types'; import { renameProfile } from '../../../api/quality-profiles'; import Modal from '../../../components/controls/Modal'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { Profile } from '../types'; interface Props { onClose: () => void; @@ -47,11 +48,6 @@ export default class RenameProfileForm extends React.PureComponent<Props, State> this.mounted = false; } - handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => { this.setState({ name: event.currentTarget.value }); }; @@ -112,12 +108,12 @@ export default class RenameProfileForm extends React.PureComponent<Props, State> </div> <div className="modal-foot"> {this.state.loading && <i className="spinner spacer-right" />} - <button disabled={submitDisabled} id="rename-profile-submit"> + <SubmitButton disabled={submitDisabled} id="rename-profile-submit"> {translate('rename')} - </button> - <a href="#" id="rename-profile-cancel" onClick={this.handleCancelClick}> + </SubmitButton> + <ResetButtonLink id="rename-profile-cancel" onClick={this.props.onClose}> {translate('cancel')} - </a> + </ResetButtonLink> </div> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.tsx index b1978c6d310..b783eb7e411 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.tsx @@ -19,11 +19,12 @@ */ import * as React from 'react'; import { sortBy } from 'lodash'; +import { Profile } from '../types'; import { changeProfileParent } from '../../../api/quality-profiles'; import Modal from '../../../components/controls/Modal'; import Select from '../../../components/controls/Select'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; -import { Profile } from '../types'; interface Props { onChange: () => void; @@ -53,11 +54,6 @@ export default class ChangeParentForm extends React.PureComponent<Props, State> this.mounted = false; } - handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - handleSelectChange = (option: { value: string }) => { this.setState({ selected: option.value }); }; @@ -126,12 +122,12 @@ export default class ChangeParentForm extends React.PureComponent<Props, State> </div> <div className="modal-foot"> {this.state.loading && <i className="spinner spacer-right" />} - <button disabled={submitDisabled} id="change-profile-parent-submit"> + <SubmitButton disabled={submitDisabled} id="change-profile-parent-submit"> {translate('change_verb')} - </button> - <a href="#" id="change-profile-parent-cancel" onClick={this.handleCancelClick}> + </SubmitButton> + <ResetButtonLink id="change-profile-parent-cancel" onClick={this.props.onClose}> {translate('cancel')} - </a> + </ResetButtonLink> </div> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.tsx index 2786757c0f3..85deb056f70 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.tsx @@ -21,9 +21,10 @@ import * as React from 'react'; import * as classNames from 'classnames'; import ProfileInheritanceBox from './ProfileInheritanceBox'; import ChangeParentForm from './ChangeParentForm'; -import { translate } from '../../../helpers/l10n'; -import { getProfileInheritance } from '../../../api/quality-profiles'; import { Profile } from '../types'; +import { getProfileInheritance } from '../../../api/quality-profiles'; +import { Button } from '../../../components/ui/buttons'; +import { translate } from '../../../helpers/l10n'; interface Props { onRequestFail: (reason: any) => void; @@ -74,21 +75,27 @@ export default class ProfileInheritance extends React.PureComponent<Props, State } loadData() { - getProfileInheritance(this.props.profile.key).then((r: any) => { - if (this.mounted) { - const { ancestors, children } = r; - this.setState({ - children, - ancestors: ancestors.reverse(), - profile: r.profile, - loading: false - }); + getProfileInheritance(this.props.profile.key).then( + r => { + if (this.mounted) { + const { ancestors, children } = r; + this.setState({ + children, + ancestors: ancestors.reverse(), + profile: r.profile, + loading: false + }); + } + }, + () => { + if (this.mounted) { + this.setState({ loading: false }); + } } - }); + ); } - handleChangeParentClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); + handleChangeParentClick = () => { this.setState({ formOpen: true }); }; @@ -123,11 +130,11 @@ export default class ProfileInheritance extends React.PureComponent<Props, State profile.actions.edit && !profile.isBuiltIn && ( <div className="boxed-group-actions"> - <button + <Button className="pull-right js-change-parent" onClick={this.handleChangeParentClick}> {translate('quality_profiles.change_parent')} - </button> + </Button> </div> )} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissions.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissions.tsx index e77208909df..ad88c22f0a1 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissions.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissions.tsx @@ -27,6 +27,7 @@ import { searchGroups, SearchUsersGroupsParameters } from '../../../api/quality-profiles'; +import { Button } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; export interface User { @@ -100,9 +101,7 @@ export default class ProfilePermissions extends React.PureComponent<Props, State ); } - handleAddUserButtonClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); + handleAddUserButtonClick = () => { this.setState({ addUserForm: true }); }; @@ -180,9 +179,9 @@ export default class ProfilePermissions extends React.PureComponent<Props, State /> ))} <div className="text-right"> - <button onClick={this.handleAddUserButtonClick}> + <Button onClick={this.handleAddUserButtonClick}> {translate('quality_profiles.grant_permissions_to_more_users')} - </button> + </Button> </div> </div> )} @@ -193,8 +192,8 @@ export default class ProfilePermissions extends React.PureComponent<Props, State onClose={this.handleAddUserFormClose} onGroupAdd={this.handleGroupAdd} onUserAdd={this.handleUserAdd} - profile={this.props.profile} organization={this.props.organization} + profile={this.props.profile} /> )} </div> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissionsForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissionsForm.tsx index 06fc7c571da..cb5a652065a 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissionsForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissionsForm.tsx @@ -28,6 +28,7 @@ import { SearchUsersGroupsParameters } from '../../../api/quality-profiles'; import Modal from '../../../components/controls/Modal'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; interface Props { @@ -121,20 +122,16 @@ export default class ProfilePermissionsForm extends React.PureComponent<Props, S <div className="modal-large-field"> <label>{translate('quality_profiles.search_description')}</label> <ProfilePermissionsFormSelect - selected={this.state.selected} onChange={this.handleValueChange} onSearch={this.handleSearch} + selected={this.state.selected} /> </div> </div> <footer className="modal-foot"> {this.state.submitting && <i className="spinner spacer-right" />} - <button disabled={submitDisabled} type="submit"> - {translate('add_verb')} - </button> - <button className="button-link" onClick={this.props.onClose} type="reset"> - {translate('cancel')} - </button> + <SubmitButton disabled={submitDisabled}>{translate('add_verb')}</SubmitButton> + <ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink> </footer> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissionsGroup.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissionsGroup.tsx index 2dbb0885ee2..7e859a8fe38 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissionsGroup.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissionsGroup.tsx @@ -23,7 +23,7 @@ import { Group } from './ProfilePermissions'; import { removeGroup } from '../../../api/quality-profiles'; import SimpleModal, { ChildrenProps } from '../../../components/controls/SimpleModal'; import GroupIcon from '../../../components/icons-components/GroupIcon'; -import { DeleteButton } from '../../../components/ui/buttons'; +import { DeleteButton, Button, ResetButtonLink } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; interface Props { @@ -91,12 +91,10 @@ export default class ProfilePermissionsGroup extends React.PureComponent<Props, <footer className="modal-foot"> {props.submitting && <i className="spinner spacer-right" />} - <button className="button-red" disabled={props.submitting} onClick={props.onSubmitClick}> + <Button className="button-red" disabled={props.submitting} onClick={props.onSubmitClick}> {translate('remove')} - </button> - <a href="#" onClick={props.onCloseClick}> - {translate('cancel')} - </a> + </Button> + <ResetButtonLink onClick={props.onCloseClick}>{translate('cancel')}</ResetButtonLink> </footer> </div> ); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissionsUser.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissionsUser.tsx index d2c2094cd62..f35c21287e4 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissionsUser.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissionsUser.tsx @@ -22,7 +22,7 @@ import { FormattedMessage } from 'react-intl'; import { User } from './ProfilePermissions'; import { removeUser } from '../../../api/quality-profiles'; import SimpleModal, { ChildrenProps } from '../../../components/controls/SimpleModal'; -import { DeleteButton } from '../../../components/ui/buttons'; +import { DeleteButton, SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; import Avatar from '../../../components/ui/Avatar'; import { translate } from '../../../helpers/l10n'; @@ -91,12 +91,13 @@ export default class ProfilePermissionsUser extends React.PureComponent<Props, S <footer className="modal-foot"> {props.submitting && <i className="spinner spacer-right" />} - <button className="button-red" disabled={props.submitting} onClick={props.onSubmitClick}> + <SubmitButton + className="button-red" + disabled={props.submitting} + onClick={props.onSubmitClick}> {translate('remove')} - </button> - <a href="#" onClick={props.onCloseClick}> - {translate('cancel')} - </a> + </SubmitButton> + <ResetButtonLink onClick={props.onCloseClick}>{translate('cancel')}</ResetButtonLink> </footer> </div> ); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx index 84fd60c40fa..7ac978ea69e 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx @@ -20,10 +20,11 @@ import * as React from 'react'; import { Link } from 'react-router'; import ChangeProjectsForm from './ChangeProjectsForm'; -import QualifierIcon from '../../../components/shared/QualifierIcon'; +import { Profile } from '../types'; import { getProfileProjects } from '../../../api/quality-profiles'; +import QualifierIcon from '../../../components/shared/QualifierIcon'; +import { Button } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; -import { Profile } from '../types'; interface Props { organization: string | null; @@ -83,8 +84,7 @@ export default class ProfileProjects extends React.PureComponent<Props, State> { ); } - handleChangeClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); + handleChangeClick = () => { this.setState({ formOpen: true }); }; @@ -124,10 +124,10 @@ export default class ProfileProjects extends React.PureComponent<Props, State> { return ( <ul> {projects.map(project => ( - <li key={project.uuid} className="spacer-top js-profile-project" data-key={project.key}> + <li className="spacer-top js-profile-project" data-key={project.key} key={project.uuid}> <Link - to={{ pathname: '/dashboard', query: { id: project.key } }} - className="link-with-icon"> + className="link-with-icon" + to={{ pathname: '/dashboard', query: { id: project.key } }}> <QualifierIcon qualifier="TRK" /> <span>{project.name}</span> </Link> </li> @@ -143,9 +143,9 @@ export default class ProfileProjects extends React.PureComponent<Props, State> { {profile.actions && profile.actions.associateProjects && ( <div className="boxed-group-actions"> - <button className="js-change-projects" onClick={this.handleChangeClick}> + <Button className="js-change-projects" onClick={this.handleChangeClick}> {translate('quality_profiles.change_projects')} - </button> + </Button> </div> )} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfilePermissions-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfilePermissions-test.tsx index 08452e22416..ff5d9b2def5 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfilePermissions-test.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfilePermissions-test.tsx @@ -59,7 +59,7 @@ it('opens add users form', async () => { await waitAndUpdate(wrapper); expect(wrapper.find('ProfilePermissionsForm').exists()).toBeFalsy(); - click(wrapper.find('button')); + click(wrapper.find('Button')); expect(wrapper.find('ProfilePermissionsForm').exists()).toBeTruthy(); wrapper.find('ProfilePermissionsForm').prop<Function>('onClose')(); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfilePermissionsGroup-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfilePermissionsGroup-test.tsx index e106e5a231c..fa4d4662aa7 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfilePermissionsGroup-test.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfilePermissionsGroup-test.tsx @@ -25,6 +25,7 @@ jest.mock('../../../../api/quality-profiles', () => ({ import * as React from 'react'; import { shallow } from 'enzyme'; import ProfilePermissionsGroup from '../ProfilePermissionsGroup'; +import { click } from '../../../../helpers/testUtils'; const removeGroup = require('../../../../api/quality-profiles').removeGroup as jest.Mock<any>; @@ -54,8 +55,7 @@ it('removes user', async () => { (wrapper.instance() as ProfilePermissionsGroup).mounted = true; expect(wrapper.find('SimpleModal').exists()).toBeFalsy(); - wrapper.find('DeleteButton').prop<Function>('onClick')(); - wrapper.update(); + click(wrapper.find('DeleteButton')); expect(wrapper.find('SimpleModal').exists()).toBeTruthy(); wrapper.find('SimpleModal').prop<Function>('onSubmit')(); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfilePermissionsUser-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfilePermissionsUser-test.tsx index e26d1cf7322..7a9b63d9c4f 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfilePermissionsUser-test.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfilePermissionsUser-test.tsx @@ -25,6 +25,7 @@ jest.mock('../../../../api/quality-profiles', () => ({ import * as React from 'react'; import { shallow } from 'enzyme'; import ProfilePermissionsUser from '../ProfilePermissionsUser'; +import { click } from '../../../../helpers/testUtils'; const removeUser = require('../../../../api/quality-profiles').removeUser as jest.Mock<any>; @@ -49,8 +50,7 @@ it('removes user', async () => { (wrapper.instance() as ProfilePermissionsUser).mounted = true; expect(wrapper.find('SimpleModal').exists()).toBeFalsy(); - wrapper.find('DeleteButton').prop<Function>('onClick')(); - wrapper.update(); + click(wrapper.find('DeleteButton')); expect(wrapper.find('SimpleModal').exists()).toBeTruthy(); wrapper.find('SimpleModal').prop<Function>('onSubmit')(); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfilePermissions-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfilePermissions-test.tsx.snap index 9dd2c1d6c47..29599454c98 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfilePermissions-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfilePermissions-test.tsx.snap @@ -78,11 +78,11 @@ exports[`renders 2`] = ` <div className="text-right" > - <button + <Button onClick={[Function]} > quality_profiles.grant_permissions_to_more_users - </button> + </Button> </div> </div> </div> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfilePermissionsForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfilePermissionsForm-test.tsx.snap index a4e51e5f160..4caa41a48c1 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfilePermissionsForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfilePermissionsForm-test.tsx.snap @@ -33,19 +33,16 @@ exports[`adds group 1`] = ` <footer className="modal-foot" > - <button + <SubmitButton disabled={true} - type="submit" > add_verb - </button> - <button - className="button-link" + </SubmitButton> + <ResetButtonLink onClick={[MockFunction]} - type="reset" > cancel - </button> + </ResetButtonLink> </footer> </form> </Modal> @@ -89,19 +86,16 @@ exports[`adds group 2`] = ` <footer className="modal-foot" > - <button + <SubmitButton disabled={false} - type="submit" > add_verb - </button> - <button - className="button-link" + </SubmitButton> + <ResetButtonLink onClick={[MockFunction]} - type="reset" > cancel - </button> + </ResetButtonLink> </footer> </form> </Modal> @@ -148,19 +142,16 @@ exports[`adds group 3`] = ` <i className="spinner spacer-right" /> - <button + <SubmitButton disabled={true} - type="submit" > add_verb - </button> - <button - className="button-link" + </SubmitButton> + <ResetButtonLink onClick={[MockFunction]} - type="reset" > cancel - </button> + </ResetButtonLink> </footer> </form> </Modal> @@ -199,19 +190,16 @@ exports[`adds user 1`] = ` <footer className="modal-foot" > - <button + <SubmitButton disabled={true} - type="submit" > add_verb - </button> - <button - className="button-link" + </SubmitButton> + <ResetButtonLink onClick={[MockFunction]} - type="reset" > cancel - </button> + </ResetButtonLink> </footer> </form> </Modal> @@ -255,19 +243,16 @@ exports[`adds user 2`] = ` <footer className="modal-foot" > - <button + <SubmitButton disabled={false} - type="submit" > add_verb - </button> - <button - className="button-link" + </SubmitButton> + <ResetButtonLink onClick={[MockFunction]} - type="reset" > cancel - </button> + </ResetButtonLink> </footer> </form> </Modal> @@ -314,19 +299,16 @@ exports[`adds user 3`] = ` <i className="spinner spacer-right" /> - <button + <SubmitButton disabled={true} - type="submit" > add_verb - </button> - <button - className="button-link" + </SubmitButton> + <ResetButtonLink onClick={[MockFunction]} - type="reset" > cancel - </button> + </ResetButtonLink> </footer> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx index d60ab75b1d2..e9fcc95b3ad 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx @@ -22,6 +22,7 @@ import { sortBy } from 'lodash'; import { getImporters, createQualityProfile } from '../../../api/quality-profiles'; import Modal from '../../../components/controls/Modal'; import Select from '../../../components/controls/Select'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; interface Props { @@ -55,19 +56,19 @@ export default class CreateProfileForm extends React.PureComponent<Props, State> fetchImporters() { getImporters().then( - (importers: Array<{ key: string; languages: Array<string>; name: string }>) => { + importers => { if (this.mounted) { this.setState({ importers, preloading: false }); } + }, + () => { + if (this.mounted) { + this.setState({ preloading: false }); + } } ); } - handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => { this.setState({ name: event.currentTarget.value }); }; @@ -171,20 +172,20 @@ export default class CreateProfileForm extends React.PureComponent<Props, State> </div> ))} {/* drop me when we stop supporting ie11 */} - <input type="hidden" name="hello-ie11" value="" /> + <input name="hello-ie11" type="hidden" value="" /> </div> )} <div className="modal-foot"> {this.state.loading && <i className="spinner spacer-right" />} {!this.state.preloading && ( - <button disabled={this.state.loading} id="create-profile-submit"> + <SubmitButton disabled={this.state.loading} id="create-profile-submit"> {translate('create')} - </button> + </SubmitButton> )} - <a href="#" id="create-profile-cancel" onClick={this.handleCancelClick}> + <ResetButtonLink id="create-profile-cancel" onClick={this.props.onClose}> {translate('cancel')} - </a> + </ResetButtonLink> </div> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx index 796daea5b23..1373fa0f309 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx @@ -21,10 +21,11 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import CreateProfileForm from './CreateProfileForm'; import RestoreProfileForm from './RestoreProfileForm'; -import { getProfilePath } from '../utils'; -import { translate } from '../../../helpers/l10n'; import { Profile } from '../types'; +import { getProfilePath } from '../utils'; import { Actions } from '../../../api/quality-profiles'; +import { Button } from '../../../components/ui/buttons'; +import { translate } from '../../../helpers/l10n'; interface Props { actions: Actions; @@ -49,26 +50,26 @@ export default class PageHeader extends React.PureComponent<Props, State> { restoreFormOpen: false }; - handleCreateClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); - event.currentTarget.blur(); + handleCreateClick = () => { this.setState({ createFormOpen: true }); }; handleCreate = (profile: Profile) => { - this.props.updateProfiles().then(() => { - this.context.router.push( - getProfilePath(profile.name, profile.language, this.props.organization) - ); - }); + this.props.updateProfiles().then( + () => { + this.context.router.push( + getProfilePath(profile.name, profile.language, this.props.organization) + ); + }, + () => {} + ); }; closeCreateForm = () => { this.setState({ createFormOpen: false }); }; - handleRestoreClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); + handleRestoreClick = () => { this.setState({ restoreFormOpen: true }); }; @@ -83,15 +84,15 @@ export default class PageHeader extends React.PureComponent<Props, State> { {this.props.actions.create && ( <div className="page-actions"> - <button id="quality-profiles-create" onClick={this.handleCreateClick}> + <Button id="quality-profiles-create" onClick={this.handleCreateClick}> {translate('create')} - </button> - <button + </Button> + <Button className="little-spacer-left" id="quality-profiles-restore" onClick={this.handleRestoreClick}> {translate('restore')} - </button> + </Button> </div> )} @@ -114,8 +115,8 @@ export default class PageHeader extends React.PureComponent<Props, State> { <CreateProfileForm languages={this.props.languages} onClose={this.closeCreateForm} - onRequestFail={this.props.onRequestFail} onCreate={this.handleCreate} + onRequestFail={this.props.onRequestFail} organization={this.props.organization} /> )} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/RestoreProfileForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/RestoreProfileForm.tsx index d964ff019ee..a93f0e0a44e 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/RestoreProfileForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/RestoreProfileForm.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import { restoreQualityProfile } from '../../../api/quality-profiles'; import Modal from '../../../components/controls/Modal'; import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; interface Props { onClose: () => void; @@ -48,11 +49,6 @@ export default class RestoreProfileForm extends React.PureComponent<Props, State this.mounted = false; } - handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { event.preventDefault(); @@ -130,18 +126,18 @@ export default class RestoreProfileForm extends React.PureComponent<Props, State {ruleSuccesses == null ? ( <div className="modal-foot"> {loading && <i className="spinner spacer-right" />} - <button disabled={loading} id="restore-profile-submit"> + <SubmitButton disabled={loading} id="restore-profile-submit"> {translate('restore')} - </button> - <a href="#" id="restore-profile-cancel" onClick={this.handleCancelClick}> + </SubmitButton> + <ResetButtonLink id="restore-profile-cancel" onClick={this.props.onClose}> {translate('cancel')} - </a> + </ResetButtonLink> </div> ) : ( <div className="modal-foot"> - <a href="#" onClick={this.handleCancelClick}> + <ResetButtonLink id="restore-profile-cancel" onClick={this.props.onClose}> {translate('close')} - </a> + </ResetButtonLink> </div> )} </form> diff --git a/server/sonar-web/src/main/js/apps/sessions/components/LoginForm.tsx b/server/sonar-web/src/main/js/apps/sessions/components/LoginForm.tsx index 136f0b00522..b793c09a985 100644 --- a/server/sonar-web/src/main/js/apps/sessions/components/LoginForm.tsx +++ b/server/sonar-web/src/main/js/apps/sessions/components/LoginForm.tsx @@ -22,6 +22,7 @@ import { Link } from 'react-router'; import OAuthProviders from './OAuthProviders'; import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer'; import { IdentityProvider } from '../../../app/types'; +import { SubmitButton } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; import './LoginForm.css'; @@ -94,44 +95,42 @@ export default class LoginForm extends React.PureComponent<Props, State> { <GlobalMessagesContainer /> <div className="big-spacer-bottom"> - <label htmlFor="login" className="login-label"> + <label className="login-label" htmlFor="login"> {translate('login')} </label> <input - type="text" - id="login" - name="login" + autoFocus={true} className="login-input" + id="login" maxLength={255} - required={true} - autoFocus={true} + name="login" + onChange={this.handleLoginChange} placeholder={translate('login')} + required={true} + type="text" value={this.state.login} - onChange={this.handleLoginChange} /> </div> <div className="big-spacer-bottom"> - <label htmlFor="password" className="login-label"> + <label className="login-label" htmlFor="password"> {translate('password')} </label> <input - type="password" + className="login-input" id="password" name="password" - className="login-input" - required={true} + onChange={this.handlePwdChange} placeholder={translate('password')} + required={true} + type="password" value={this.state.password} - onChange={this.handlePwdChange} /> </div> <div> <div className="text-right overflow-hidden"> - <button name="commit" type="submit"> - {translate('sessions.log_in')} - </button> + <SubmitButton>{translate('sessions.log_in')}</SubmitButton> <Link className="spacer-left" to="/"> {translate('cancel')} </Link> diff --git a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginForm-test.tsx.snap index fddd44f6bcd..744c0b93b09 100644 --- a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginForm-test.tsx.snap @@ -111,12 +111,9 @@ exports[`expands more options 2`] = ` <div className="text-right overflow-hidden" > - <button - name="commit" - type="submit" - > + <SubmitButton> sessions.log_in - </button> + </SubmitButton> <Link className="spacer-left" onlyActiveOnIndex={false} @@ -229,12 +226,9 @@ exports[`logs in with simple credentials 1`] = ` <div className="text-right overflow-hidden" > - <button - name="commit" - type="submit" - > + <SubmitButton> sessions.log_in - </button> + </SubmitButton> <Link className="spacer-left" onlyActiveOnIndex={false} diff --git a/server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx b/server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx index f3d70706104..fd80e756d09 100644 --- a/server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx @@ -18,10 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { LOGS_LEVELS } from '../utils'; import { setLogLevel } from '../../../api/system'; import Modal from '../../../components/controls/Modal'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; -import { LOGS_LEVELS } from '../utils'; interface Props { infoMsg: string; @@ -41,11 +42,6 @@ export default class ChangeLogLevelForm extends React.PureComponent<Props, State this.state = { newLevel: props.logLevel, updating: false }; } - handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { event.preventDefault(); const { newLevel } = this.state; @@ -72,15 +68,15 @@ export default class ChangeLogLevelForm extends React.PureComponent<Props, State </div> <div className="modal-body"> {LOGS_LEVELS.map(level => ( - <p key={level} className="spacer-bottom"> + <p className="spacer-bottom" key={level}> <input - id={`loglevel-${level}`} - type="radio" + checked={level === newLevel} className="spacer-right text-middle" + id={`loglevel-${level}`} name="system.log_levels" - value={level} - checked={level === newLevel} onChange={this.handleLevelChange} + type="radio" + value={level} /> <label className="text-middle" htmlFor={`loglevel-${level}`}> {level} @@ -96,12 +92,12 @@ export default class ChangeLogLevelForm extends React.PureComponent<Props, State </div> <div className="modal-foot"> {updating && <i className="spinner spacer-right" />} - <button disabled={updating} id="set-log-level-submit"> + <SubmitButton disabled={updating} id="set-log-level-submit"> {translate('save')} - </button> - <a href="#" id="set-log-level-cancel" onClick={this.handleCancelClick}> + </SubmitButton> + <ResetButtonLink id="set-log-level-cancel" onClick={this.props.onClose}> {translate('cancel')} - </a> + </ResetButtonLink> </div> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/system/components/PageActions.tsx b/server/sonar-web/src/main/js/apps/system/components/PageActions.tsx index dfda4b9e904..7b718fc5ad2 100644 --- a/server/sonar-web/src/main/js/apps/system/components/PageActions.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/PageActions.tsx @@ -21,7 +21,7 @@ import * as React from 'react'; import ChangeLogLevelForm from './ChangeLogLevelForm'; import RestartForm from '../../../components/common/RestartForm'; import { getFileNameSuffix } from '../utils'; -import { EditButton } from '../../../components/ui/buttons'; +import { EditButton, Button } from '../../../components/ui/buttons'; import { getBaseUrl } from '../../../helpers/urls'; import { translate } from '../../../helpers/l10n'; @@ -66,12 +66,21 @@ export default class PageActions extends React.PureComponent<Props, State> { this.handleLogsLevelClose(); }; - handleLogsLevelClose = () => this.setState({ openLogsLevelForm: false }); + handleLogsLevelClose = () => { + this.setState({ openLogsLevelForm: false }); + }; + + handleServerRestartOpen = () => { + this.setState({ openRestartForm: true }); + }; - handleServerRestartOpen = () => this.setState({ openRestartForm: true }); - handleServerRestartClose = () => this.setState({ openRestartForm: false }); + handleServerRestartClose = () => { + this.setState({ openRestartForm: false }); + }; - removeElementFocus = (event: React.SyntheticEvent<HTMLElement>) => event.currentTarget.blur(); + removeElementFocus = (event: React.SyntheticEvent<HTMLElement>) => { + event.currentTarget.blur(); + }; render() { const infoUrl = getBaseUrl() + '/api/system/info'; @@ -85,50 +94,51 @@ export default class PageActions extends React.PureComponent<Props, State> { <strong className="little-spacer-left">{this.state.logLevel}</strong> </span> <EditButton - id="edit-logs-level-button" className="spacer-left button-small" + id="edit-logs-level-button" onClick={this.handleLogsLevelOpen} /> </span> {this.props.canDownloadLogs && ( <div className="display-inline-block dropdown spacer-left"> - <button data-toggle="dropdown"> + {/* TODO use Dropdown component */} + <Button data-toggle="dropdown"> {translate('system.download_logs')} <i className="icon-dropdown little-spacer-left" /> - </button> + </Button> <ul className="dropdown-menu"> <li> <a + download="sonarqube_app.log" href={logsUrl + '?process=app'} id="logs-link" - download="sonarqube_app.log" target="_blank"> Main Process </a> </li> <li> <a + download="sonarqube_ce.log" href={logsUrl + '?process=ce'} id="ce-logs-link" - download="sonarqube_ce.log" target="_blank"> Compute Engine </a> </li> <li> <a + download="sonarqube_es.log" href={logsUrl + '?process=es'} id="es-logs-link" - download="sonarqube_es.log" target="_blank"> Search Engine </a> </li> <li> <a + download="sonarqube_web.log" href={logsUrl + '?process=web'} id="web-logs-link" - download="sonarqube_web.log" target="_blank"> Web Server </a> @@ -137,21 +147,21 @@ export default class PageActions extends React.PureComponent<Props, State> { </div> )} <a + className="button spacer-left" + download={`sonarqube-support-info-${getFileNameSuffix(this.props.serverId)}.json`} href={infoUrl} id="download-link" - className="button spacer-left" onClick={this.removeElementFocus} - download={`sonarqube-support-info-${getFileNameSuffix(this.props.serverId)}.json`} target="_blank"> {translate('system.download_system_info')} </a> {this.props.canRestart && ( - <button - id="restart-server-button" + <Button className="spacer-left" + id="restart-server-button" onClick={this.handleServerRestartOpen}> {translate('system.restart_server')} - </button> + </Button> )} {this.props.canRestart && this.state.openRestartForm && <RestartForm onClose={this.handleServerRestartClose} />} diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap index 0c857453ed9..d65fc8d4872 100644 --- a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap @@ -93,19 +93,18 @@ exports[`should display some warning messages for non INFO levels 1`] = ` <div className="modal-foot" > - <button + <SubmitButton disabled={false} id="set-log-level-submit" > save - </button> - <a - href="#" + </SubmitButton> + <ResetButtonLink id="set-log-level-cancel" onClick={[Function]} > cancel - </a> + </ResetButtonLink> </div> </form> </Modal> @@ -199,19 +198,18 @@ exports[`should render correctly 1`] = ` <div className="modal-foot" > - <button + <SubmitButton disabled={false} id="set-log-level-submit" > save - </button> - <a - href="#" + </SubmitButton> + <ResetButtonLink id="set-log-level-cancel" onClick={[Function]} > cancel - </a> + </ResetButtonLink> </div> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageActions-test.tsx.snap index dd7d6f39b36..252b7a95398 100644 --- a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageActions-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageActions-test.tsx.snap @@ -25,14 +25,14 @@ exports[`should render correctly 1`] = ` <div className="display-inline-block dropdown spacer-left" > - <button + <Button data-toggle="dropdown" > system.download_logs <i className="icon-dropdown little-spacer-left" /> - </button> + </Button> <ul className="dropdown-menu" > @@ -88,13 +88,13 @@ exports[`should render correctly 1`] = ` > system.download_system_info </a> - <button + <Button className="spacer-left" id="restart-server-button" onClick={[Function]} > system.restart_server - </button> + </Button> </div> `; diff --git a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeNotif.tsx b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeNotif.tsx index a39c44965b3..15f4e8a3b10 100644 --- a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeNotif.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeNotif.tsx @@ -19,9 +19,10 @@ */ import * as React from 'react'; import SystemUpgradeForm from './SystemUpgradeForm'; +import { sortUpgrades, groupUpgrades } from '../../utils'; import { getSystemUpgrades, SystemUpgrade } from '../../../../api/system'; +import { Button } from '../../../../components/ui/buttons'; import { translate } from '../../../../helpers/l10n'; -import { sortUpgrades, groupUpgrades } from '../../utils'; interface State { systemUpgrades: SystemUpgrade[][]; @@ -51,8 +52,13 @@ export default class SystemUpgradeNotif extends React.PureComponent<{}, State> { () => {} ); - handleOpenSystemUpgradeForm = () => this.setState({ openSystemUpgradeForm: true }); - handleCloseSystemUpgradeForm = () => this.setState({ openSystemUpgradeForm: false }); + handleOpenSystemUpgradeForm = () => { + this.setState({ openSystemUpgradeForm: true }); + }; + + handleCloseSystemUpgradeForm = () => { + this.setState({ openSystemUpgradeForm: false }); + }; render() { const { systemUpgrades } = this.state; @@ -65,14 +71,14 @@ export default class SystemUpgradeNotif extends React.PureComponent<{}, State> { <div className="page-notifs"> <div className="alert alert-info"> {translate('system.new_version_available')} - <button className="spacer-left" onClick={this.handleOpenSystemUpgradeForm}> + <Button className="spacer-left" onClick={this.handleOpenSystemUpgradeForm}> {translate('learn_more')} - </button> + </Button> </div> {this.state.openSystemUpgradeForm && ( <SystemUpgradeForm - systemUpgrades={systemUpgrades} onClose={this.handleCloseSystemUpgradeForm} + systemUpgrades={systemUpgrades} /> )} </div> diff --git a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeNotif-test.tsx b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeNotif-test.tsx index 0ae8979a9d7..29d8533cd82 100644 --- a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeNotif-test.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeNotif-test.tsx @@ -103,6 +103,6 @@ it('should fetch upgrade when mounting', () => { it('should open the upgrade form', async () => { const wrapper = shallow(<SystemUpgradeNotif />); await waitAndUpdate(wrapper); - click(wrapper.find('button')); + click(wrapper.find('Button')); expect(wrapper.find('SystemUpgradeForm').exists()).toBeTruthy(); }); diff --git a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeNotif-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeNotif-test.tsx.snap index f1b0b865020..50c3d8ee70e 100644 --- a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeNotif-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeNotif-test.tsx.snap @@ -8,12 +8,12 @@ exports[`should display correctly 1`] = ` className="alert alert-info" > system.new_version_available - <button + <Button className="spacer-left" onClick={[Function]} > learn_more - </button> + </Button> </div> </div> `; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewOrganizationForm-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewOrganizationForm-test.js.snap index 40da82493a8..7694dfc182d 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewOrganizationForm-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewOrganizationForm-test.js.snap @@ -93,17 +93,29 @@ exports[`creates new organization 3`] = ` color="#d4333f" onClick={[Function]} > - <button + <Button className="button-small button-icon" onClick={[Function]} + stopPropagation={true} style={ Object { "color": "#d4333f", } } > - <ClearIcon /> - </button> + <button + className="button button-small button-icon" + onClick={[Function]} + style={ + Object { + "color": "#d4333f", + } + } + type="button" + > + <ClearIcon /> + </button> + </Button> </ButtonIcon> </DeleteButton> </div> @@ -130,17 +142,29 @@ exports[`deletes organization 1`] = ` color="#d4333f" onClick={[Function]} > - <button + <Button className="button-small button-icon" onClick={[Function]} + stopPropagation={true} style={ Object { "color": "#d4333f", } } > - <ClearIcon /> - </button> + <button + className="button button-small button-icon" + onClick={[Function]} + style={ + Object { + "color": "#d4333f", + } + } + type="button" + > + <ClearIcon /> + </button> + </Button> </ButtonIcon> </DeleteButton> </div> diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewProjectForm-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewProjectForm-test.js.snap index 5f389a66391..a6682fb7870 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewProjectForm-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/NewProjectForm-test.js.snap @@ -117,17 +117,29 @@ exports[`creates new project 3`] = ` color="#d4333f" onClick={[Function]} > - <button + <Button className="button-small text-middle button-icon" onClick={[Function]} + stopPropagation={true} style={ Object { "color": "#d4333f", } } > - <ClearIcon /> - </button> + <button + className="button button-small text-middle button-icon" + onClick={[Function]} + style={ + Object { + "color": "#d4333f", + } + } + type="button" + > + <ClearIcon /> + </button> + </Button> </ButtonIcon> </DeleteButton> </div> @@ -163,17 +175,29 @@ exports[`deletes project 1`] = ` color="#d4333f" onClick={[Function]} > - <button + <Button className="button-small text-middle button-icon" onClick={[Function]} + stopPropagation={true} style={ Object { "color": "#d4333f", } } > - <ClearIcon /> - </button> + <button + className="button button-small text-middle button-icon" + onClick={[Function]} + style={ + Object { + "color": "#d4333f", + } + } + type="button" + > + <ClearIcon /> + </button> + </Button> </ButtonIcon> </DeleteButton> </div> diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap index 332780eddec..86d6f2a627c 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/TokenStep-test.js.snap @@ -264,17 +264,29 @@ exports[`generates token 3`] = ` color="#d4333f" onClick={[Function]} > - <button + <Button className="button-small text-middle button-icon" onClick={[Function]} + stopPropagation={true} style={ Object { "color": "#d4333f", } } > - <ClearIcon /> - </button> + <button + className="button button-small text-middle button-icon" + onClick={[Function]} + style={ + Object { + "color": "#d4333f", + } + } + type="button" + > + <ClearIcon /> + </button> + </Button> </ButtonIcon> </DeleteButton> </form> @@ -362,17 +374,29 @@ exports[`revokes token 1`] = ` color="#d4333f" onClick={[Function]} > - <button + <Button className="button-small text-middle button-icon" onClick={[Function]} + stopPropagation={true} style={ Object { "color": "#d4333f", } } > - <ClearIcon /> - </button> + <button + className="button button-small text-middle button-icon" + onClick={[Function]} + style={ + Object { + "color": "#d4333f", + } + } + type="button" + > + <ClearIcon /> + </button> + </Button> </ButtonIcon> </DeleteButton> </form> diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/Command-test.js.snap b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/Command-test.js.snap index 2bd0aafd5b8..a7a6a001caa 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/Command-test.js.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/Command-test.js.snap @@ -17,13 +17,22 @@ bar bar" tooltipPlacement="top" > - <button + <Button className="js-copy-to-clipboard no-select" data-clipboard-text="foo bar" + innerRef={[Function]} > - copy - </button> + <button + className="button js-copy-to-clipboard no-select" + data-clipboard-text="foo +bar" + onClick={[Function]} + type="button" + > + copy + </button> + </Button> </ClipboardButton> </div> </Command> diff --git a/server/sonar-web/src/main/js/apps/users/Header.tsx b/server/sonar-web/src/main/js/apps/users/Header.tsx index 7131a7eb0f0..65808704194 100644 --- a/server/sonar-web/src/main/js/apps/users/Header.tsx +++ b/server/sonar-web/src/main/js/apps/users/Header.tsx @@ -20,6 +20,7 @@ import * as React from 'react'; import UserForm from './components/UserForm'; import DeferredSpinner from '../../components/common/DeferredSpinner'; +import { Button } from '../../components/ui/buttons'; import { translate } from '../../helpers/l10n'; interface Props { @@ -34,19 +35,24 @@ interface State { export default class Header extends React.PureComponent<Props, State> { state: State = { openUserForm: false }; - handleOpenUserForm = () => this.setState({ openUserForm: true }); - handleCloseUserForm = () => this.setState({ openUserForm: false }); + handleOpenUserForm = () => { + this.setState({ openUserForm: true }); + }; + + handleCloseUserForm = () => { + this.setState({ openUserForm: false }); + }; render() { return ( - <header id="users-header" className="page-header"> + <header className="page-header" id="users-header"> <h1 className="page-title">{translate('users.page')}</h1> <DeferredSpinner loading={this.props.loading} /> <div className="page-actions"> - <button id="users-create" onClick={this.handleOpenUserForm}> + <Button id="users-create" onClick={this.handleOpenUserForm}> {translate('users.create_user')} - </button> + </Button> </div> <p className="page-description">{translate('users.page.description')}</p> diff --git a/server/sonar-web/src/main/js/apps/users/__tests__/__snapshots__/Header-test.tsx.snap b/server/sonar-web/src/main/js/apps/users/__tests__/__snapshots__/Header-test.tsx.snap index 6e38124302c..7acecae4f39 100644 --- a/server/sonar-web/src/main/js/apps/users/__tests__/__snapshots__/Header-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/users/__tests__/__snapshots__/Header-test.tsx.snap @@ -17,12 +17,12 @@ exports[`should render correctly 1`] = ` <div className="page-actions" > - <button + <Button id="users-create" onClick={[Function]} > users.create_user - </button> + </Button> </div> <p className="page-description" diff --git a/server/sonar-web/src/main/js/apps/users/components/DeactivateForm.tsx b/server/sonar-web/src/main/js/apps/users/components/DeactivateForm.tsx index 7b34c4c3fc8..062a463d444 100644 --- a/server/sonar-web/src/main/js/apps/users/components/DeactivateForm.tsx +++ b/server/sonar-web/src/main/js/apps/users/components/DeactivateForm.tsx @@ -18,9 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import Modal from '../../../components/controls/Modal'; import { deactivateUser } from '../../../api/users'; import { User } from '../../../app/types'; +import Modal from '../../../components/controls/Modal'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; import { translate, translateWithParameters } from '../../../helpers/l10n'; export interface Props { @@ -45,11 +46,6 @@ export default class DeactivateForm extends React.PureComponent<Props, State> { this.mounted = false; } - handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - handleDeactivate = (event: React.SyntheticEvent<HTMLFormElement>) => { event.preventDefault(); this.setState({ submitting: true }); @@ -73,7 +69,7 @@ export default class DeactivateForm extends React.PureComponent<Props, State> { const header = translate('users.deactivate_user'); return ( <Modal contentLabel={header} onRequestClose={this.props.onClose}> - <form id="deactivate-user-form" onSubmit={this.handleDeactivate} autoComplete="off"> + <form autoComplete="off" id="deactivate-user-form" onSubmit={this.handleDeactivate}> <header className="modal-head"> <h2>{header}</h2> </header> @@ -82,12 +78,12 @@ export default class DeactivateForm extends React.PureComponent<Props, State> { </div> <footer className="modal-foot"> {submitting && <i className="spinner spacer-right" />} - <button className="js-confirm button-red" disabled={submitting} type="submit"> + <SubmitButton className="js-confirm button-red" disabled={submitting}> {translate('users.deactivate')} - </button> - <a className="js-modal-close" href="#" onClick={this.handleCancelClick}> + </SubmitButton> + <ResetButtonLink className="js-modal-close" onClick={this.props.onClose}> {translate('cancel')} - </a> + </ResetButtonLink> </footer> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/users/components/PasswordForm.tsx b/server/sonar-web/src/main/js/apps/users/components/PasswordForm.tsx index 675b42761ce..00872246c35 100644 --- a/server/sonar-web/src/main/js/apps/users/components/PasswordForm.tsx +++ b/server/sonar-web/src/main/js/apps/users/components/PasswordForm.tsx @@ -18,13 +18,14 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import Modal from '../../../components/controls/Modal'; +import { changePassword } from '../../../api/users'; +import { User } from '../../../app/types'; import addGlobalSuccessMessage from '../../../app/utils/addGlobalSuccessMessage'; import throwGlobalError from '../../../app/utils/throwGlobalError'; -import { User } from '../../../app/types'; -import { parseError } from '../../../helpers/request'; -import { changePassword } from '../../../api/users'; +import Modal from '../../../components/controls/Modal'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; +import { parseError } from '../../../helpers/request'; interface Props { isCurrentUser: boolean; @@ -77,11 +78,6 @@ export default class PasswordForm extends React.PureComponent<Props, State> { handleOldPasswordChange = (event: React.SyntheticEvent<HTMLInputElement>) => this.setState({ oldPassword: event.currentTarget.value }); - handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - handleChangePassword = (event: React.SyntheticEvent<HTMLFormElement>) => { event.preventDefault(); if ( @@ -106,7 +102,7 @@ export default class PasswordForm extends React.PureComponent<Props, State> { const header = translate('my_profile.password.title'); return ( <Modal contentLabel={header} onRequestClose={this.props.onClose}> - <form id="user-password-form" onSubmit={this.handleChangePassword} autoComplete="off"> + <form autoComplete="off" id="user-password-form" onSubmit={this.handleChangePassword}> <header className="modal-head"> <h2>{header}</h2> </header> @@ -119,14 +115,14 @@ export default class PasswordForm extends React.PureComponent<Props, State> { <em className="mandatory">*</em> </label> {/* keep this fake field to hack browser autofill */} - <input name="old-password-fake" type="password" className="hidden" /> + <input className="hidden" name="old-password-fake" type="password" /> <input id="old-user-password" - name="old-password" - type="password" maxLength={50} + name="old-password" onChange={this.handleOldPasswordChange} required={true} + type="password" value={this.state.oldPassword} /> </div> @@ -137,14 +133,14 @@ export default class PasswordForm extends React.PureComponent<Props, State> { <em className="mandatory">*</em> </label> {/* keep this fake field to hack browser autofill */} - <input name="password-fake" type="password" className="hidden" /> + <input className="hidden" name="password-fake" type="password" /> <input id="user-password" - name="password" - type="password" maxLength={50} + name="password" onChange={this.handleNewPasswordChange} required={true} + type="password" value={this.state.newPassword} /> </div> @@ -154,29 +150,28 @@ export default class PasswordForm extends React.PureComponent<Props, State> { <em className="mandatory">*</em> </label> {/* keep this fake field to hack browser autofill */} - <input name="confirm-password-fake" type="password" className="hidden" /> + <input className="hidden" name="confirm-password-fake" type="password" /> <input id="confirm-user-password" - name="confirm-password" - type="password" maxLength={50} + name="confirm-password" onChange={this.handleConfirmPasswordChange} required={true} + type="password" value={this.state.confirmPassword} /> </div> </div> <footer className="modal-foot"> {submitting && <i className="spinner spacer-right" />} - <button + <SubmitButton className="js-confirm" - disabled={submitting || !newPassword || newPassword !== confirmPassword} - type="submit"> + disabled={submitting || !newPassword || newPassword !== confirmPassword}> {translate('change_verb')} - </button> - <a className="js-modal-close" href="#" onClick={this.handleCancelClick}> + </SubmitButton> + <ResetButtonLink className="js-modal-close" onClick={this.props.onClose}> {translate('cancel')} - </a> + </ResetButtonLink> </footer> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx b/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx index 0869b13fbaf..98d6c1e234e 100644 --- a/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx +++ b/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx @@ -20,8 +20,9 @@ import * as React from 'react'; import TokensFormItem from './TokensFormItem'; import TokensFormNewToken from './TokensFormNewToken'; -import DeferredSpinner from '../../../components/common/DeferredSpinner'; import { getTokens, generateToken, UserToken } from '../../../api/user-tokens'; +import DeferredSpinner from '../../../components/common/DeferredSpinner'; +import { SubmitButton } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; interface Props { @@ -119,7 +120,7 @@ export default class TokensForm extends React.PureComponent<Props, State> { if (tokens.length <= 0) { return ( <tr> - <td colSpan={3} className="note"> + <td className="note" colSpan={3}> {translate('users.no_tokens')} </td> </tr> @@ -129,8 +130,8 @@ export default class TokensForm extends React.PureComponent<Props, State> { <TokensFormItem key={token.name} login={this.props.login} - token={token} onRevokeToken={this.handleRevokeToken} + token={token} /> )); } @@ -147,22 +148,21 @@ export default class TokensForm extends React.PureComponent<Props, State> { return ( <> <h3 className="spacer-bottom">{translate('users.generate_tokens')}</h3> - <form id="generate-token-form" onSubmit={this.handleGenerateToken} autoComplete="off"> + <form autoComplete="off" id="generate-token-form" onSubmit={this.handleGenerateToken}> <input className="spacer-right" - type="text" maxLength={100} onChange={this.handleNewTokenChange} placeholder={translate('users.enter_token_name')} required={true} + type="text" value={newTokenName} /> - <button + <SubmitButton className="js-generate-token" - disabled={generating || newTokenName.length <= 0} - type="submit"> + disabled={generating || newTokenName.length <= 0}> {translate('users.generate')} - </button> + </SubmitButton> </form> {newToken && <TokensFormNewToken token={newToken} />} diff --git a/server/sonar-web/src/main/js/apps/users/components/TokensFormItem.tsx b/server/sonar-web/src/main/js/apps/users/components/TokensFormItem.tsx index ab1a4902aa7..fb48879e3b5 100644 --- a/server/sonar-web/src/main/js/apps/users/components/TokensFormItem.tsx +++ b/server/sonar-web/src/main/js/apps/users/components/TokensFormItem.tsx @@ -18,12 +18,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { revokeToken, UserToken } from '../../../api/user-tokens'; +import DeferredSpinner from '../../../components/common/DeferredSpinner'; import Tooltip from '../../../components/controls/Tooltip'; import DateFormatter from '../../../components/intl/DateFormatter'; -import DeferredSpinner from '../../../components/common/DeferredSpinner'; -import { revokeToken, UserToken } from '../../../api/user-tokens'; -import { limitComponentName } from '../../../helpers/path'; +import { Button } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; +import { limitComponentName } from '../../../helpers/path'; interface Props { login: string; @@ -81,14 +82,14 @@ export default class TokensFormItem extends React.PureComponent<Props, State> { <DeferredSpinner loading={loading}> <i className="spinner-placeholder " /> </DeferredSpinner> - <button + <Button className="button-red input-small spacer-left" - onClick={this.handleRevoke} - disabled={loading}> + disabled={loading} + onClick={this.handleRevoke}> {this.state.deleting ? translate('users.tokens.sure') : translate('users.tokens.revoke')} - </button> + </Button> </td> </tr> ); diff --git a/server/sonar-web/src/main/js/apps/users/components/UserForm.tsx b/server/sonar-web/src/main/js/apps/users/components/UserForm.tsx index 6aae2138038..95ccccce126 100644 --- a/server/sonar-web/src/main/js/apps/users/components/UserForm.tsx +++ b/server/sonar-web/src/main/js/apps/users/components/UserForm.tsx @@ -20,12 +20,13 @@ import * as React from 'react'; import { uniq } from 'lodash'; import UserScmAccountInput from './UserScmAccountInput'; -import Modal from '../../../components/controls/Modal'; -import throwGlobalError from '../../../app/utils/throwGlobalError'; -import { parseError } from '../../../helpers/request'; import { createUser, updateUser } from '../../../api/users'; import { User } from '../../../app/types'; +import throwGlobalError from '../../../app/utils/throwGlobalError'; +import Modal from '../../../components/controls/Modal'; +import { Button, SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { parseError } from '../../../helpers/request'; export interface Props { user?: User; @@ -101,11 +102,6 @@ export default class UserForm extends React.PureComponent<Props, State> { handlePasswordChange = (event: React.SyntheticEvent<HTMLInputElement>) => this.setState({ password: event.currentTarget.value }); - handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - handleCreateUser = (event: React.SyntheticEvent<HTMLFormElement>) => { event.preventDefault(); this.setState({ submitting: true }); @@ -135,8 +131,7 @@ export default class UserForm extends React.PureComponent<Props, State> { }, this.handleError); }; - handleAddScmAccount = (evt: React.SyntheticEvent<HTMLButtonElement>) => { - evt.preventDefault(); + handleAddScmAccount = () => { this.setState(({ scmAccounts }) => ({ scmAccounts: scmAccounts.concat('') })); }; @@ -160,9 +155,9 @@ export default class UserForm extends React.PureComponent<Props, State> { return ( <Modal contentLabel={header} onRequestClose={this.props.onClose}> <form + autoComplete="off" id="user-form" - onSubmit={this.props.user ? this.handleUpdateUser : this.handleCreateUser} - autoComplete="off"> + onSubmit={this.props.user ? this.handleUpdateUser : this.handleCreateUser}> <header className="modal-head"> <h2>{header}</h2> </header> @@ -177,15 +172,15 @@ export default class UserForm extends React.PureComponent<Props, State> { <em className="mandatory">*</em> </label> {/* keep this fake field to hack browser autofill */} - <input name="login-fake" type="text" className="hidden" /> + <input className="hidden" name="login-fake" type="text" /> <input id="create-user-login" - name="login" - type="text" - minLength={3} maxLength={255} + minLength={3} + name="login" onChange={this.handleLoginChange} required={true} + type="text" value={this.state.login} /> <p className="note">{translateWithParameters('users.minimum_x_characters', 3)}</p> @@ -197,27 +192,27 @@ export default class UserForm extends React.PureComponent<Props, State> { <em className="mandatory">*</em> </label> {/* keep this fake field to hack browser autofill */} - <input name="name-fake" type="text" className="hidden" /> + <input className="hidden" name="name-fake" type="text" /> <input id="create-user-name" - name="name" - type="text" maxLength={200} + name="name" onChange={this.handleNameChange} required={true} + type="text" value={this.state.name} /> </div> <div className="modal-field"> <label htmlFor="create-user-email">{translate('users.email')}</label> {/* keep this fake field to hack browser autofill */} - <input name="email-fake" type="email" className="hidden" /> + <input className="hidden" name="email-fake" type="email" /> <input id="create-user-email" - name="email" - type="email" maxLength={100} + name="email" onChange={this.handleEmailChange} + type="email" value={this.state.email} /> </div> @@ -228,14 +223,14 @@ export default class UserForm extends React.PureComponent<Props, State> { <em className="mandatory">*</em> </label> {/* keep this fake field to hack browser autofill */} - <input name="password-fake" type="password" className="hidden" /> + <input className="hidden" name="password-fake" type="password" /> <input id="create-user-password" - name="password" - type="password" maxLength={50} + name="password" onChange={this.handlePasswordChange} required={true} + type="password" value={this.state.password} /> </div> @@ -252,7 +247,9 @@ export default class UserForm extends React.PureComponent<Props, State> { /> ))} <div className="spacer-bottom"> - <button onClick={this.handleAddScmAccount}>{translate('add_verb')}</button> + <Button onClick={this.handleAddScmAccount} type="reset"> + {translate('add_verb')} + </Button> </div> <p className="note">{translate('user.login_or_email_used_as_scm_account')}</p> </div> @@ -260,12 +257,12 @@ export default class UserForm extends React.PureComponent<Props, State> { <footer className="modal-foot"> {submitting && <i className="spinner spacer-right" />} - <button className="js-confirm" disabled={submitting} type="submit"> + <SubmitButton className="js-confirm" disabled={submitting}> {user ? translate('update_verb') : translate('create')} - </button> - <a className="js-modal-close" href="#" onClick={this.handleCancelClick}> + </SubmitButton> + <ResetButtonLink className="js-modal-close" onClick={this.props.onClose}> {translate('cancel')} - </a> + </ResetButtonLink> </footer> </form> </Modal> diff --git a/server/sonar-web/src/main/js/components/common/FiltersHeader.tsx b/server/sonar-web/src/main/js/components/common/FiltersHeader.tsx index 33567469928..6d643c45df7 100644 --- a/server/sonar-web/src/main/js/components/common/FiltersHeader.tsx +++ b/server/sonar-web/src/main/js/components/common/FiltersHeader.tsx @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { Button } from '../ui/buttons'; import { translate } from '../../helpers/l10n'; interface Props { @@ -25,29 +26,18 @@ interface Props { onReset: () => void; } -export default class FiltersHeader extends React.PureComponent<Props> { - handleResetClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); - this.props.onReset(); - }; +export default function FiltersHeader({ displayReset, onReset }: Props) { + return ( + <div className="search-navigator-filters-header"> + {displayReset && ( + <div className="pull-right"> + <Button className="button-red" id="coding-rules-clear-all-filters" onClick={onReset}> + {translate('clear_all_filters')} + </Button> + </div> + )} - render() { - return ( - <div className="search-navigator-filters-header"> - {this.props.displayReset && ( - <div className="pull-right"> - <button - className="button-red" - id="coding-rules-clear-all-filters" - onClick={this.handleResetClick}> - {translate('clear_all_filters')} - </button> - </div> - )} - - <h3>{translate('filters')}</h3> - </div> - ); - } + <h3>{translate('filters')}</h3> + </div> + ); } diff --git a/server/sonar-web/src/main/js/components/common/RestartForm.tsx b/server/sonar-web/src/main/js/components/common/RestartForm.tsx index 54b694ef5cb..eb4f31f7cfb 100644 --- a/server/sonar-web/src/main/js/components/common/RestartForm.tsx +++ b/server/sonar-web/src/main/js/components/common/RestartForm.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import * as classNames from 'classnames'; import { restartAndWait } from '../../api/system'; import Modal from '../../components/controls/Modal'; +import { SubmitButton, ResetButtonLink } from '../ui/buttons'; import { translate } from '../../helpers/l10n'; interface Props { @@ -34,11 +35,6 @@ interface State { export default class RestartForm extends React.PureComponent<Props, State> { state: State = { restarting: false }; - handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); - this.props.onClose(); - }; - handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { event.preventDefault(); if (!this.state.restarting) { @@ -71,14 +67,13 @@ export default class RestartForm extends React.PureComponent<Props, State> { </div> {!restarting && ( <div className="modal-foot"> - <button id="restart-server-submit">{translate('restart')}</button> - <a - href="#" + <SubmitButton id="restart-server-submit">{translate('restart')}</SubmitButton> + <ResetButtonLink className="js-modal-close" id="restart-server-cancel" - onClick={this.handleCancelClick}> + onClick={this.props.onClose}> {translate('cancel')} - </a> + </ResetButtonLink> </div> )} </form> diff --git a/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx b/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx index 39ad938beee..a20842524a6 100644 --- a/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx +++ b/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx @@ -22,6 +22,7 @@ import * as classNames from 'classnames'; import { Link } from 'react-router'; import { LocationDescriptor } from 'history'; import SettingsIcon from '../icons-components/SettingsIcon'; +import { Button } from '../ui/buttons'; interface Props { className?: string; @@ -37,7 +38,7 @@ interface Props { export default function ActionsDropdown({ menuPosition = 'right', ...props }: Props) { return ( <div className={classNames('dropdown', props.className)}> - <button + <Button className={classNames('dropdown-toggle', props.toggleClassName, { 'button-small': props.small })} @@ -45,7 +46,7 @@ export default function ActionsDropdown({ menuPosition = 'right', ...props }: Pr onClick={props.onToggleClick}> <SettingsIcon className="text-text-bottom" /> <i className="icon-dropdown little-spacer-left" /> - </button> + </Button> <ul className={classNames('dropdown-menu', props.menuClassName, { 'dropdown-menu-right': menuPosition === 'right' diff --git a/server/sonar-web/src/main/js/components/controls/ClipboardButton.tsx b/server/sonar-web/src/main/js/components/controls/ClipboardButton.tsx index e7d0ed049f9..6969fc0271f 100644 --- a/server/sonar-web/src/main/js/components/controls/ClipboardButton.tsx +++ b/server/sonar-web/src/main/js/components/controls/ClipboardButton.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import * as classNames from 'classnames'; import * as Clipboard from 'clipboard'; import Tooltip from './Tooltip'; +import { Button } from '../ui/buttons'; import { translate } from '../../helpers/l10n'; interface Props { @@ -35,7 +36,7 @@ interface State { export default class ClipboardButton extends React.PureComponent<Props, State> { clipboard?: Clipboard; - copyButton?: HTMLButtonElement | null; + copyButton?: HTMLElement | null; mounted = false; state: State = { tooltipShown: false }; @@ -77,19 +78,19 @@ export default class ClipboardButton extends React.PureComponent<Props, State> { render() { const button = ( - <button + <Button className={classNames('js-copy-to-clipboard no-select', this.props.className)} data-clipboard-text={this.props.copyValue} - ref={node => (this.copyButton = node)}> + innerRef={node => (this.copyButton = node)}> {translate('copy')} - </button> + </Button> ); if (this.state.tooltipShown) { return ( <Tooltip defaultVisible={true} - placement={this.props.tooltipPlacement || 'bottom'} overlay={translate('copied_action')} + placement={this.props.tooltipPlacement || 'bottom'} trigger="manual"> {button} </Tooltip> diff --git a/server/sonar-web/src/main/js/components/controls/ConfirmButton.tsx b/server/sonar-web/src/main/js/components/controls/ConfirmButton.tsx index 54934d9e8ac..0f05db2ac90 100644 --- a/server/sonar-web/src/main/js/components/controls/ConfirmButton.tsx +++ b/server/sonar-web/src/main/js/components/controls/ConfirmButton.tsx @@ -21,11 +21,10 @@ import * as React from 'react'; import SimpleModal from './SimpleModal'; import DeferredSpinner from '../common/DeferredSpinner'; import { translate } from '../../helpers/l10n'; +import { SubmitButton, ResetButtonLink } from '../ui/buttons'; interface Props { - children: ( - props: { onClick: (event?: React.SyntheticEvent<HTMLButtonElement>) => void } - ) => React.ReactNode; + children: (props: { onClick: () => void }) => React.ReactNode; confirmButtonText: string; confirmData?: string; isDestructive?: boolean; @@ -50,11 +49,7 @@ export default class ConfirmButton extends React.PureComponent<Props, State> { this.mounted = false; } - handleButtonClick = (event?: React.SyntheticEvent<HTMLButtonElement>) => { - if (event) { - event.preventDefault(); - event.currentTarget.blur(); - } + handleButtonClick = () => { this.setState({ modal: true }); }; @@ -85,8 +80,8 @@ export default class ConfirmButton extends React.PureComponent<Props, State> { header={modalHeader} onClose={this.handleCloseModal} onSubmit={this.handleSubmit}> - {({ onCloseClick, onSubmitClick, submitting }) => ( - <> + {({ onCloseClick, onFormSubmit, submitting }) => ( + <form onSubmit={onFormSubmit}> <header className="modal-head"> <h2>{modalHeader}</h2> </header> @@ -95,17 +90,14 @@ export default class ConfirmButton extends React.PureComponent<Props, State> { <footer className="modal-foot"> <DeferredSpinner className="spacer-right" loading={submitting} /> - <button + <SubmitButton className={isDestructive ? 'button-red' : undefined} - disabled={submitting} - onClick={onSubmitClick}> + disabled={submitting}> {confirmButtonText} - </button> - <a href="#" onClick={onCloseClick}> - {translate('cancel')} - </a> + </SubmitButton> + <ResetButtonLink onClick={onCloseClick}>{translate('cancel')}</ResetButtonLink> </footer> - </> + </form> )} </SimpleModal> )} diff --git a/server/sonar-web/src/main/js/components/controls/Dropdown.tsx b/server/sonar-web/src/main/js/components/controls/Dropdown.tsx index 1022dc7fc02..3b82fd1c408 100644 --- a/server/sonar-web/src/main/js/components/controls/Dropdown.tsx +++ b/server/sonar-web/src/main/js/components/controls/Dropdown.tsx @@ -21,7 +21,7 @@ import * as React from 'react'; interface RenderProps { closeDropdown: () => void; - onToggleClick: (event: React.SyntheticEvent<HTMLElement>) => void; + onToggleClick: (event?: React.SyntheticEvent<HTMLElement>) => void; open: boolean; } @@ -75,10 +75,12 @@ export default class Dropdown extends React.PureComponent<Props, State> { closeDropdown = () => this.setState({ open: false }); - handleToggleClick = (event: React.SyntheticEvent<HTMLElement>) => { - this.toggleNode = event.currentTarget; - event.preventDefault(); - event.currentTarget.blur(); + handleToggleClick = (event?: React.SyntheticEvent<HTMLElement>) => { + if (event) { + this.toggleNode = event.currentTarget; + event.preventDefault(); + event.currentTarget.blur(); + } this.setState(state => ({ open: !state.open })); }; diff --git a/server/sonar-web/src/main/js/components/controls/SimpleModal.tsx b/server/sonar-web/src/main/js/components/controls/SimpleModal.tsx index b277510245d..e85d13031ed 100644 --- a/server/sonar-web/src/main/js/components/controls/SimpleModal.tsx +++ b/server/sonar-web/src/main/js/components/controls/SimpleModal.tsx @@ -21,9 +21,9 @@ import * as React from 'react'; import Modal from '../../components/controls/Modal'; export interface ChildrenProps { - onCloseClick: (event: React.SyntheticEvent<HTMLElement>) => void; + onCloseClick: (event?: React.SyntheticEvent<HTMLElement>) => void; onFormSubmit: (event: React.SyntheticEvent<HTMLFormElement>) => void; - onSubmitClick: (event: React.SyntheticEvent<HTMLElement>) => void; + onSubmitClick: (event?: React.SyntheticEvent<HTMLElement>) => void; submitting: boolean; } @@ -56,9 +56,11 @@ export default class SimpleModal extends React.PureComponent<Props, State> { } }; - handleCloseClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); - event.currentTarget.blur(); + handleCloseClick = (event?: React.SyntheticEvent<HTMLElement>) => { + if (event) { + event.preventDefault(); + event.currentTarget.blur(); + } this.props.onClose(); }; @@ -67,9 +69,11 @@ export default class SimpleModal extends React.PureComponent<Props, State> { this.submit(); }; - handleSubmitClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); - event.currentTarget.blur(); + handleSubmitClick = (event?: React.SyntheticEvent<HTMLElement>) => { + if (event) { + event.preventDefault(); + event.currentTarget.blur(); + } this.submit(); }; diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/Dropdown-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/Dropdown-test.tsx index 84653beb24e..096dc8356ba 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/Dropdown-test.tsx +++ b/server/sonar-web/src/main/js/components/controls/__tests__/Dropdown-test.tsx @@ -20,6 +20,7 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import Dropdown from '../Dropdown'; +import { Button } from '../../ui/buttons'; import { click } from '../../../helpers/testUtils'; it('renders', () => { @@ -32,13 +33,13 @@ it('renders', () => { it('toggles', () => { const wrapper = shallow( - <Dropdown>{({ onToggleClick }) => <button onClick={onToggleClick} />}</Dropdown> + <Dropdown>{({ onToggleClick }) => <Button onClick={onToggleClick} />}</Dropdown> ); expect(wrapper.state()).toEqual({ open: false }); - click(wrapper.find('button')); + click(wrapper.find('Button')); expect(wrapper.state()).toEqual({ open: true }); - click(wrapper.find('button')); + click(wrapper.find('Button')); expect(wrapper.state()).toEqual({ open: false }); }); diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/SimpleModal-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/SimpleModal-test.tsx index c4ea074996c..dd07b347bb1 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/SimpleModal-test.tsx +++ b/server/sonar-web/src/main/js/components/controls/__tests__/SimpleModal-test.tsx @@ -20,6 +20,7 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import SimpleModal, { ChildrenProps } from '../SimpleModal'; +import { Button } from '../../ui/buttons'; import { click, waitAndUpdate } from '../../../helpers/testUtils'; it('renders', () => { @@ -35,22 +36,22 @@ it('renders', () => { it('closes', () => { const onClose = jest.fn(); - const inner = ({ onCloseClick }: ChildrenProps) => <button onClick={onCloseClick}>close</button>; + const inner = ({ onCloseClick }: ChildrenProps) => <Button onClick={onCloseClick}>close</Button>; const wrapper = shallow( <SimpleModal header="" onClose={onClose} onSubmit={jest.fn()}> {inner} </SimpleModal> ); - click(wrapper.find('button')); + click(wrapper.find('Button')); expect(onClose).toBeCalled(); }); it('submits', async () => { const onSubmit = jest.fn(() => Promise.resolve()); const inner = ({ onSubmitClick, submitting }: ChildrenProps) => ( - <button disabled={submitting} onClick={onSubmitClick}> + <Button disabled={submitting} onClick={onSubmitClick}> close - </button> + </Button> ); const wrapper = shallow( <SimpleModal header="" onClose={jest.fn()} onSubmit={onSubmit}> @@ -60,7 +61,7 @@ it('submits', async () => { (wrapper.instance() as SimpleModal).mounted = true; expect(wrapper).toMatchSnapshot(); - click(wrapper.find('button')); + click(wrapper.find('Button')); expect(onSubmit).toBeCalled(); expect(wrapper).toMatchSnapshot(); diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ClipboardButton-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ClipboardButton-test.tsx.snap index 61870f73365..4ad6724747b 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ClipboardButton-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ClipboardButton-test.tsx.snap @@ -1,12 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should display correctly 1`] = ` -<button +<Button className="js-copy-to-clipboard no-select" data-clipboard-text="foo" + innerRef={[Function]} > copy -</button> +</Button> `; exports[`should display correctly 2`] = ` @@ -16,11 +17,12 @@ exports[`should display correctly 2`] = ` placement="bottom" trigger="manual" > - <button + <Button className="js-copy-to-clipboard no-select" data-clipboard-text="foo" + innerRef={[Function]} > copy - </button> + </Button> </Tooltip> `; diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/SimpleModal-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/SimpleModal-test.tsx.snap index 359048021d7..49b14a9e20f 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/SimpleModal-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/SimpleModal-test.tsx.snap @@ -14,12 +14,12 @@ exports[`submits 1`] = ` contentLabel="" onRequestClose={[MockFunction]} > - <button + <Button disabled={false} onClick={[Function]} > close - </button> + </Button> </Modal> `; @@ -28,12 +28,12 @@ exports[`submits 2`] = ` contentLabel="" onRequestClose={[MockFunction]} > - <button + <Button disabled={true} onClick={[Function]} > close - </button> + </Button> </Modal> `; @@ -42,11 +42,11 @@ exports[`submits 3`] = ` contentLabel="" onRequestClose={[MockFunction]} > - <button + <Button disabled={false} onClick={[Function]} > close - </button> + </Button> </Modal> `; diff --git a/server/sonar-web/src/main/js/components/facet/FacetHeader.tsx b/server/sonar-web/src/main/js/components/facet/FacetHeader.tsx index cfe3f068bc4..f1394dba898 100644 --- a/server/sonar-web/src/main/js/components/facet/FacetHeader.tsx +++ b/server/sonar-web/src/main/js/components/facet/FacetHeader.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import OpenCloseIcon from '../icons-components/OpenCloseIcon'; import HelpIcon from '../icons-components/HelpIcon'; import Tooltip from '../controls/Tooltip'; +import { Button } from '../ui/buttons'; import { translate, translateWithParameters } from '../../helpers/l10n'; interface Props { @@ -33,14 +34,6 @@ interface Props { } export default class FacetHeader extends React.PureComponent<Props> { - handleClearClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); - event.currentTarget.blur(); - if (this.props.onClear) { - this.props.onClear(); - } - }; - handleClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { event.preventDefault(); event.currentTarget.blur(); @@ -102,11 +95,11 @@ export default class FacetHeader extends React.PureComponent<Props> { </span> {showClearButton && ( - <button + <Button className="search-navigator-facet-header-button button-small button-red" - onClick={this.handleClearClick}> + onClick={this.props.onClear}> {translate('clear')} - </button> + </Button> )} </div> ); diff --git a/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetHeader-test.tsx.snap b/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetHeader-test.tsx.snap index dad6166c959..e68fe594ca1 100644 --- a/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetHeader-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetHeader-test.tsx.snap @@ -28,12 +28,12 @@ exports[`should clear 1`] = ` x_selected.3 </span> </span> - <button + <Button className="search-navigator-facet-header-button button-small button-red" - onClick={[Function]} + onClick={[MockFunction]} > clear - </button> + </Button> </div> `; diff --git a/server/sonar-web/src/main/js/components/ui/buttons.css b/server/sonar-web/src/main/js/components/ui/buttons.css index 5d1d7b0410c..da474c888c4 100644 --- a/server/sonar-web/src/main/js/components/ui/buttons.css +++ b/server/sonar-web/src/main/js/components/ui/buttons.css @@ -17,7 +17,10 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -.button-icon { + +/* use double selector .button-icon.button-icon to increase the specificity */ + +.button-icon.button-icon { display: inline-flex; justify-content: center; align-items: center; @@ -29,28 +32,28 @@ color: inherit; } -.button-icon.button-small { +.button-icon.button-icon.button-small { width: var(--smallControlHeight); height: var(--smallControlHeight); padding: 0; } -.button-icon.button-small svg { +.button-icon.button-icon.button-small svg { margin-top: 0; } -.button-icon.button-tiny { +.button-icon.button-icon.button-tiny { width: var(--tinyControlHeight); height: var(--tinyControlHeight); padding: 0; } -.button-icon:hover, -.button-icon:focus { +.button-icon.button-icon:hover, +.button-icon.button-icon:focus { background-color: currentColor; } -.button-icon:hover svg, -.button-icon:focus svg { +.button-icon.button-icon:hover svg, +.button-icon.button-icon:focus svg { color: #fff; } diff --git a/server/sonar-web/src/main/js/components/ui/buttons.tsx b/server/sonar-web/src/main/js/components/ui/buttons.tsx index c89c0cda24c..82992ba0a64 100644 --- a/server/sonar-web/src/main/js/components/ui/buttons.tsx +++ b/server/sonar-web/src/main/js/components/ui/buttons.tsx @@ -20,50 +20,95 @@ import * as React from 'react'; import * as classNames from 'classnames'; import * as theme from '../../app/theme'; +import { Omit } from '../../app/types'; import ClearIcon from '../icons-components/ClearIcon'; import EditIcon from '../icons-components/EditIcon'; import Tooltip from '../controls/Tooltip'; import './buttons.css'; -interface ButtonIconProps { - children: React.ReactNode; +interface ButtonProps { className?: string; - color?: string; - onClick?: () => void; - tooltip?: string; - [x: string]: any; + children?: React.ReactNode; + disabled?: boolean; + id?: string; + innerRef?: (node: HTMLElement | null) => void; + onClick?: (event: React.MouseEvent<HTMLElement>) => void; + preventDefault?: boolean; + stopPropagation?: boolean; + style?: React.CSSProperties; + type?: string; } -export class ButtonIcon extends React.PureComponent<ButtonIconProps> { - handleClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { - event.preventDefault(); +export class Button extends React.PureComponent<ButtonProps> { + handleClick = (event: React.MouseEvent<HTMLButtonElement>) => { + const { onClick, preventDefault = true, stopPropagation = false } = this.props; + event.currentTarget.blur(); - event.stopPropagation(); - if (this.props.onClick) { - this.props.onClick(); - } + if (preventDefault) event.preventDefault(); + if (stopPropagation) event.stopPropagation(); + if (onClick) onClick(event); }; render() { - const { children, className, color = theme.darkBlue, onClick, tooltip, ...props } = this.props; - const buttonComponent = ( + const { + className, + innerRef, + onClick, + preventDefault, + stopPropagation, + type = 'button', + ...props + } = this.props; + return ( + // eslint-disable-next-line react/button-has-type <button - className={classNames(className, 'button-icon')} + {...props} + className={classNames('button', className)} + disabled={this.props.disabled} + id={this.props.id} onClick={this.handleClick} - style={{ color }} - {...props}> - {children} - </button> + ref={this.props.innerRef} + type={type} + /> + ); + } +} + +export function SubmitButton(props: Omit<ButtonProps, 'type'>) { + // do not prevent default to actually submit a form + return <Button {...props} preventDefault={false} type="submit" />; +} + +export function ResetButtonLink({ className, ...props }: Omit<ButtonProps, 'type'>) { + return <Button {...props} className={classNames('button-link', className)} type="reset" />; +} + +interface ButtonIconProps { + className?: string; + color?: string; + onClick?: () => void; + tooltip?: string; + [x: string]: any; +} + +export function ButtonIcon(props: ButtonIconProps) { + const { className, color = theme.darkBlue, tooltip, ...other } = props; + const buttonComponent = ( + <Button + className={classNames(className, 'button-icon')} + stopPropagation={true} + style={{ color }} + {...other} + /> + ); + if (tooltip) { + return ( + <Tooltip mouseEnterDelay={0.4} overlay={tooltip}> + {buttonComponent} + </Tooltip> ); - if (tooltip) { - return ( - <Tooltip overlay={tooltip} mouseEnterDelay={0.4}> - {buttonComponent} - </Tooltip> - ); - } - return buttonComponent; } + return buttonComponent; } interface ActionButtonProps { diff --git a/server/sonar-web/src/main/js/helpers/testUtils.ts b/server/sonar-web/src/main/js/helpers/testUtils.ts index 5e7ad5f1d4c..bdd52ec8e1e 100644 --- a/server/sonar-web/src/main/js/helpers/testUtils.ts +++ b/server/sonar-web/src/main/js/helpers/testUtils.ts @@ -28,7 +28,15 @@ export const mockEvent = { }; export function click(element: ShallowWrapper | ReactWrapper, event = {}): void { - element.simulate('click', { ...mockEvent, ...event }); + // `type()` returns a component constructor for a composite element and string for DOM nodes + if (typeof element.type() === 'function') { + element.prop<Function>('onClick')(); + // TODO find out if `root` is a public api + // https://github.com/airbnb/enzyme/blob/master/packages/enzyme/src/ReactWrapper.js#L109 + (element as any).root().update(); + } else { + element.simulate('click', { ...mockEvent, ...event }); + } } export function clickOutside(event = {}): void { |