diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2019-02-15 17:52:52 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2019-03-06 11:30:42 +0100 |
commit | 79a32b89b9d41df6cb8f1f96980d2591a5ecb192 (patch) | |
tree | 3b800b0d0f827ab655dbc3cf356b5058b490bdf8 /server/sonar-web/src/main/js | |
parent | 769d66fa99847e63cf5536f128b53a37e7a5a430 (diff) | |
download | sonarqube-79a32b89b9d41df6cb8f1f96980d2591a5ecb192.tar.gz sonarqube-79a32b89b9d41df6cb8f1f96980d2591a5ecb192.zip |
SONARCLOUD-380 Rework modal styling of SC and SQ
* Update modal-fields
* Update form-fields styling
* Update modal-fields usage in extensions
* Clean css
Diffstat (limited to 'server/sonar-web/src/main/js')
167 files changed, 2971 insertions, 1578 deletions
diff --git a/server/sonar-web/src/main/js/app/components/StartupModal.tsx b/server/sonar-web/src/main/js/app/components/StartupModal.tsx index cbc48881b23..1162ec0ad5a 100644 --- a/server/sonar-web/src/main/js/app/components/StartupModal.tsx +++ b/server/sonar-web/src/main/js/app/components/StartupModal.tsx @@ -153,7 +153,6 @@ export class StartupModal extends React.PureComponent<Props, State> { <OnboardingModal onClose={this.closeOnboarding} onOpenProjectOnboarding={this.openProjectOnboarding} - skipOnboarding={this.props.skipOnboarding} /> )} </OnboardingContext.Provider> diff --git a/server/sonar-web/src/main/js/app/styles/components/modals.css b/server/sonar-web/src/main/js/app/styles/components/modals.css index 370030f2a61..0ded3991928 100644 --- a/server/sonar-web/src/main/js/app/styles/components/modals.css +++ b/server/sonar-web/src/main/js/app/styles/components/modals.css @@ -28,9 +28,6 @@ background-color: #fff; opacity: 0; transition: all 0.2s ease; -} - -.modal.sonarcloud { border-radius: 3px; } @@ -45,6 +42,11 @@ opacity: 1; } +.modal-small { + width: 450px; + margin-left: -225px; +} + .modal-medium { width: 830px; margin-left: -415px; @@ -86,116 +88,57 @@ margin-right: var(--sbw); } -.modal-container { - max-height: 60vh; - padding: 10px; - box-sizing: border-box; - overflow: auto; -} - -.modal.sonarcloud .modal-container { - border-top: 1px solid var(--barBorderColor); - margin-top: var(--pagePadding); -} - -.modal.sonarcloud .modal-container > :last-child { - margin-bottom: var(--pagePadding); -} - .modal-head { - padding: 0 10px; - background-color: var(--gray94); - border-bottom: 1px solid var(--disableGrayBorder); -} - -.modal.sonarcloud .modal-head { - background-color: transparent; - border-bottom: none; - padding: var(--pagePadding) calc(2 * var(--pagePadding)) 0; + padding: calc(4 * var(--gridSize)); + padding-bottom: 0; } .modal-head h1, .modal-head h2 { - line-height: 30px; - min-height: 30px; -} - -.modal.sonarcloud .modal-head h1, -.modal.sonarcloud .modal-head h2 { - margin-top: var(--gridSize); + margin: 0; font-size: var(--bigFontSize); font-weight: bold; - line-height: 30px; + line-height: normal; } .modal-body { - padding: 10px; + padding: var(--pagePadding) calc(4 * var(--gridSize)); } -.modal.sonarcloud .modal-body { - padding: var(--pagePadding) calc(2 * var(--pagePadding)); +.modal-container { + max-height: 60vh; + box-sizing: border-box; + overflow-y: scroll; + border-top: 1px solid var(--barBorderColor); + margin-top: var(--pagePadding); + padding-right: calc(4 * var(--gridSize) - var(--sbw)); +} + +.modal-container > :last-child { + margin-bottom: var(--pagePadding); } .modal-field, -.modal-large-field, .modal-validation-field { clear: both; display: block; - padding: 5px 0 5px 130px; -} - -.modal-large-field { - padding: 20px 40px; -} - -.modal-validation-field { - padding: 3px 0 3px 130px; + padding: 0; + margin-bottom: calc(var(--gridSize) * 2); } .modal-field label, .modal-validation-field label { - position: relative; - left: -140px; display: block; - float: left; - width: 120px; - margin-right: -130px; - padding-top: 5px; - padding-bottom: 2px; - padding-left: 10px; - line-height: 1; - text-align: right; - overflow: hidden; - text-overflow: ellipsis; -} - -.modal-large-field label { - display: inline-block; - padding-bottom: 15px; font-weight: bold; -} - -.modal-field .note { - line-height: var(--controlHeight); -} - -.readonly-field { - padding-top: 5px; - margin-left: -5px; - line-height: 1; + padding-bottom: calc(var(--gridSize) / 2); } .modal-field a.icon-checkbox, .modal-field input, -.modal-large-field input, .modal-field select, -.modal-large-field select, .modal-field textarea, -.modal-large-field textarea, -.modal-field .Select, -.modal-large-field .Select { +.modal-field .Select { margin-right: 5px; - margin-bottom: 10px; } .modal-field a.icon-checkbox { @@ -203,15 +146,12 @@ } .modal-field input[type='radio'], -.modal-large-field input[type='radio'], -.modal-field input[type='checkbox'], -.modal-large-field input[type='checkbox'] { +.modal-field input[type='checkbox'] { margin-top: 5px; margin-bottom: 4px; } -.modal-field > .icon-checkbox, -.modal-large-field > .icon-checkbox { +.modal-field > .icon-checkbox { padding-top: 6px; padding-right: 8px; } @@ -222,77 +162,44 @@ .modal-field textarea, .modal-field select, .modal-field .Select { - width: 250px; -} - -.modal-field textarea { - max-width: 250px; - min-width: 250px; - max-height: 50vh; - min-height: var(--controlHeight); -} - -.modal-large-field input[type='text'], -.modal-large-field input[type='email'], -.modal-large-field input[type='password'], -.modal-large-field textarea, -.modal-large-field select, -.modal-large-field .Select { width: 100%; } -.modal-large-field textarea { - max-width: 100%; - min-width: 100%; - max-height: 50vh; - min-height: var(--controlHeight); -} - .modal-validation-field input, .modal-validation-field textarea, .modal-validation-field .Select { - margin-right: 5px; + margin-right: var(--gridSize); margin-bottom: 2px; - width: 250px; + width: calc(100% - 3 * var(--gridSize)); } +.modal-field textarea, .modal-validation-field textarea { - max-width: 250px; - min-width: 250px; + max-width: 100%; + min-width: 100%; max-height: 50vh; min-height: var(--controlHeight); } - .modal-validation-field input:not(.is-invalid), .modal-validation-field .Select:not(.is-invalid) { - margin-bottom: 18px; + margin-bottom: calc(var(--tinyControlHeight) + 2px); } .modal-field-description { - padding-bottom: 4px; line-height: 1.4; color: var(--secondFontColor); font-size: var(--smallFontSize); overflow: hidden; text-overflow: ellipsis; -} - -.modal-validation-field .modal-field-description { margin-top: 2px; } .modal-foot { - padding: 10px; - border-top: 1px solid var(--disableGrayBorder); - background-color: var(--gray94); - text-align: right; -} - -.modal.sonarcloud .modal-foot { - padding: var(--pagePadding); + padding: var(--pagePadding) calc(4 * var(--gridSize)); border-top: 1px solid var(--barBorderColor); background-color: var(--barBackgroundColor); border-radius: 3px; + text-align: right; } .modal-foot button, diff --git a/server/sonar-web/src/main/js/app/styles/init/base.css b/server/sonar-web/src/main/js/app/styles/init/base.css index 67eefb1de61..af8916a1839 100644 --- a/server/sonar-web/src/main/js/app/styles/init/base.css +++ b/server/sonar-web/src/main/js/app/styles/init/base.css @@ -81,6 +81,10 @@ textarea { font-family: inherit; } +textarea { + min-height: 40px; +} + /*Remove button padding in FF*/ select::-moz-focus-inner, input::-moz-focus-inner, diff --git a/server/sonar-web/src/main/js/app/styles/init/forms.css b/server/sonar-web/src/main/js/app/styles/init/forms.css index e270290c3a5..3a820fe2894 100644 --- a/server/sonar-web/src/main/js/app/styles/init/forms.css +++ b/server/sonar-web/src/main/js/app/styles/init/forms.css @@ -160,16 +160,11 @@ select { } .input-super-large { - width: 100%; + width: 100% !important; max-width: 300px; min-width: 200px; } -textarea.input-super-large { - max-width: 600px; - min-width: 300px; -} - .input-ghost { padding: 0 !important; border: none !important; @@ -193,13 +188,31 @@ em.mandatory { .form-field { clear: both; display: block; - padding-top: var(--gridSize); padding-bottom: calc(2 * var(--gridSize)); } .form-field label { display: block; - padding-bottom: var(--gridSize); + font-weight: bold; + padding-bottom: calc(var(--gridSize) / 2); +} + +.form-field-description { + line-height: 1.4; + color: var(--secondFontColor); + font-size: var(--smallFontSize); + overflow: hidden; + text-overflow: ellipsis; + margin-top: 2px; +} + +.form-field input[type='text'], +.form-field input[type='email'], +.form-field input[type='password'], +.form-field textarea, +.form-field select, +.form-field .Select { + width: 250px; } .radio-toggle { diff --git a/server/sonar-web/src/main/js/app/styles/init/type.css b/server/sonar-web/src/main/js/app/styles/init/type.css index 2b056a346b4..36046303c59 100644 --- a/server/sonar-web/src/main/js/app/styles/init/type.css +++ b/server/sonar-web/src/main/js/app/styles/init/type.css @@ -168,6 +168,10 @@ small, font-size: var(--smallFontSize); } +.medium { + font-size: var(--mediumFontSize); +} + .big { font-size: var(--bigFontSize); } @@ -255,7 +259,7 @@ small, } .text-normal { - font-weight: normal; + font-weight: normal !important; } .text-muted { diff --git a/server/sonar-web/src/main/js/app/types.d.ts b/server/sonar-web/src/main/js/app/types.d.ts index 45d8d3e0dc2..f412c82962d 100644 --- a/server/sonar-web/src/main/js/app/types.d.ts +++ b/server/sonar-web/src/main/js/app/types.d.ts @@ -216,10 +216,7 @@ declare namespace T { value: string; } - type CurrentUserSettingNames = - | 'notifications.optOut' - | 'notifications.readDate' - | 'organizations.members.dismissSyncNotif'; + type CurrentUserSettingNames = 'notifications.optOut' | 'notifications.readDate'; export interface CustomMeasure { createdAt?: string; diff --git a/server/sonar-web/src/main/js/apps/account/components/Password.tsx b/server/sonar-web/src/main/js/apps/account/components/Password.tsx index fa681a26ec8..66e2aa43986 100644 --- a/server/sonar-web/src/main/js/apps/account/components/Password.tsx +++ b/server/sonar-web/src/main/js/apps/account/components/Password.tsx @@ -87,7 +87,7 @@ export default class Password extends React.Component<Props, State> { </Alert> ))} - <div className="modal-field"> + <div className="form-field"> <label htmlFor="old_password"> {translate('my_profile.password.old')} <em className="mandatory">*</em> @@ -101,7 +101,7 @@ export default class Password extends React.Component<Props, State> { type="password" /> </div> - <div className="modal-field"> + <div className="form-field"> <label htmlFor="password"> {translate('my_profile.password.new')} <em className="mandatory">*</em> @@ -115,7 +115,7 @@ export default class Password extends React.Component<Props, State> { type="password" /> </div> - <div className="modal-field"> + <div className="form-field"> <label htmlFor="password_confirmation"> {translate('my_profile.password.confirm')} <em className="mandatory">*</em> @@ -129,7 +129,7 @@ export default class Password extends React.Component<Props, State> { type="password" /> </div> - <div className="modal-field"> + <div className="form-field"> <SubmitButton id="change-password"> {translate('my_profile.password.submit')} </SubmitButton> diff --git a/server/sonar-web/src/main/js/components/ui/NewInfoBox.css b/server/sonar-web/src/main/js/apps/account/components/__tests__/Password-test.tsx index 0c8b0ca2a20..2b5900be381 100644 --- a/server/sonar-web/src/main/js/components/ui/NewInfoBox.css +++ b/server/sonar-web/src/main/js/apps/account/components/__tests__/Password-test.tsx @@ -17,16 +17,11 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -.new-info-box { - display: flex; - padding: var(--gridSize); - background-color: var(--veryLightBlue); - border: 1px solid var(--alertBorderInfo); - border-radius: 2px; -} +import * as React from 'react'; +import { shallow } from 'enzyme'; +import Password from '../Password'; +import { mockCurrentUser } from '../../../../helpers/testMocks'; -.new-info-box-header { - display: flex; - justify-content: space-between; - align-items: center; -} +it('renders correctly', () => { + expect(shallow(<Password user={mockCurrentUser()} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/account/components/__tests__/__snapshots__/Password-test.tsx.snap b/server/sonar-web/src/main/js/apps/account/components/__tests__/__snapshots__/Password-test.tsx.snap new file mode 100644 index 00000000000..e16485c7246 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/components/__tests__/__snapshots__/Password-test.tsx.snap @@ -0,0 +1,90 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +<section + className="boxed-group" +> + <h2 + className="spacer-bottom" + > + my_profile.password.title + </h2> + <form + className="boxed-group-inner" + onSubmit={[Function]} + > + <div + className="form-field" + > + <label + htmlFor="old_password" + > + my_profile.password.old + <em + className="mandatory" + > + * + </em> + </label> + <input + autoComplete="off" + id="old_password" + name="old_password" + required={true} + type="password" + /> + </div> + <div + className="form-field" + > + <label + htmlFor="password" + > + my_profile.password.new + <em + className="mandatory" + > + * + </em> + </label> + <input + autoComplete="off" + id="password" + name="password" + required={true} + type="password" + /> + </div> + <div + className="form-field" + > + <label + htmlFor="password_confirmation" + > + my_profile.password.confirm + <em + className="mandatory" + > + * + </em> + </label> + <input + autoComplete="off" + id="password_confirmation" + name="password_confirmation" + required={true} + type="password" + /> + </div> + <div + className="form-field" + > + <SubmitButton + id="change-password" + > + my_profile.password.submit + </SubmitButton> + </div> + </form> +</section> +`; diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/ScannerContext.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/ScannerContext.tsx index a4fe42e1914..72c73262ce1 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/ScannerContext.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/ScannerContext.tsx @@ -62,7 +62,7 @@ export default class ScannerContext extends React.PureComponent<Props, State> { const { scannerContext } = this.state; return ( - <Modal contentLabel="scanner context" large={true} onRequestClose={this.props.onClose}> + <Modal contentLabel="scanner context" onRequestClose={this.props.onClose} size={'large'}> <div className="modal-head"> <h2> {translate('background_tasks.scanner_context')} diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Stacktrace.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/Stacktrace.tsx index 1d8e2735f25..9ac1066d96f 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/Stacktrace.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Stacktrace.tsx @@ -70,7 +70,7 @@ export default class Stacktrace extends React.PureComponent<Props, State> { const { loading, stacktrace } = this.state; return ( - <Modal contentLabel="stacktrace" large={true} onRequestClose={this.props.onClose}> + <Modal contentLabel="stacktrace" onRequestClose={this.props.onClose} size={'large'}> <div className="modal-head"> <h2> {translate('background_tasks.error_stacktrace')} diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/ScannerContext-test.tsx.snap b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/ScannerContext-test.tsx.snap index dc47f7e97c5..4515f08bff8 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/ScannerContext-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/ScannerContext-test.tsx.snap @@ -3,8 +3,8 @@ exports[`renders 1`] = ` <Modal contentLabel="scanner context" - large={true} onRequestClose={[MockFunction]} + size="large" > <div className="modal-head" diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/Stacktrace-test.tsx.snap b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/Stacktrace-test.tsx.snap index 3c3c6519c93..aaf5962266d 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/Stacktrace-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/Stacktrace-test.tsx.snap @@ -3,8 +3,8 @@ exports[`renders 1`] = ` <Modal contentLabel="stacktrace" - large={true} onRequestClose={[MockFunction]} + size="large" > <div className="modal-head" 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 e9ffd00fa0d..50b5cce4633 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 @@ -21,7 +21,7 @@ import * as React from 'react'; import Modal from '../../../components/controls/Modal'; import Select from '../../../components/controls/Select'; import SeverityHelper from '../../../components/shared/SeverityHelper'; -import { activateRule, Profile as BaseProfile } from '../../../api/quality-profiles'; +import { activateRule, Profile } from '../../../api/quality-profiles'; import { SEVERITIES } from '../../../helpers/constants'; import { translate } from '../../../helpers/l10n'; import { sortProfiles } from '../../quality-profiles/utils'; @@ -34,7 +34,7 @@ interface Props { onClose: () => void; onDone: (severity: string) => Promise<void>; organization: string | undefined; - profiles: BaseProfile[]; + profiles: Profile[]; rule: T.Rule | T.RuleDetails; } @@ -153,7 +153,7 @@ export default class ActivationFormModal extends React.PureComponent<Props, Stat const isUpdateMode = !!activation; return ( - <Modal contentLabel={this.props.modalHeader} onRequestClose={this.props.onClose}> + <Modal contentLabel={this.props.modalHeader} onRequestClose={this.props.onClose} size="small"> <form onSubmit={this.handleFormSubmit}> <div className="modal-head"> <h2>{this.props.modalHeader}</h2> @@ -206,7 +206,6 @@ export default class ActivationFormModal extends React.PureComponent<Props, Stat <label title={param.key}>{param.key}</label> {param.type === 'TEXT' ? ( <textarea - className="width100" disabled={submitting} name={param.key} onChange={this.handleParameterChange} @@ -216,7 +215,6 @@ export default class ActivationFormModal extends React.PureComponent<Props, Stat /> ) : ( <input - className="input-super-large" disabled={submitting} name={param.key} onChange={this.handleParameterChange} 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 0a8158a153c..b6bf6c9d4ec 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 @@ -19,13 +19,13 @@ */ import * as React from 'react'; import { Query, serializeQuery } from '../query'; -import { Profile, bulkActivateRules, bulkDeactivateRules } from '../../../api/quality-profiles'; import Modal from '../../../components/controls/Modal'; import Select from '../../../components/controls/Select'; +import { Alert } from '../../../components/ui/Alert'; +import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; +import { Profile, bulkActivateRules, bulkDeactivateRules } from '../../../api/quality-profiles'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { formatMeasure } from '../../../helpers/measures'; -import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; -import { Alert } from '../../../components/ui/Alert'; interface Props { action: string; @@ -198,7 +198,7 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> { : `${translate('coding_rules.deactivate_in_quality_profile')} (${formatMeasure(total, 'INT')} ${translate('coding_rules._rules')})`; return ( - <Modal contentLabel={header} onRequestClose={this.props.onClose}> + <Modal contentLabel={header} onRequestClose={this.props.onClose} size="small"> <form onSubmit={this.handleFormSubmit}> <header className="modal-head"> <h2>{header}</h2> @@ -218,11 +218,11 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> { </label> </h3> {profile ? ( - <h3 className="readonly-field"> + <span> {profile.name} {' — '} {translate('are_you_sure')} - </h3> + </span> ) : ( this.renderProfileSelect() )} 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 a0d0385a8ec..c97036fd75a 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 @@ -153,76 +153,60 @@ export default class CustomRuleFormModal extends React.PureComponent<Props, Stat }; renderNameField = () => ( - <tr className="property"> - <th className="nowrap"> - <h3> - {translate('name')} <em className="mandatory">*</em> - </h3> - </th> - <td> + <div className="modal-field"> + <label htmlFor="coding-rules-custom-rule-creation-name"> + {translate('name')} <em className="mandatory">*</em> + </label> + <input + autoFocus={true} + disabled={this.state.submitting} + id="coding-rules-custom-rule-creation-name" + onChange={this.handleNameChange} + required={true} + type="text" + value={this.state.name} + /> + </div> + ); + + renderKeyField = () => ( + <div className="modal-field"> + <label htmlFor="coding-rules-custom-rule-creation-key"> + {translate('key')} {!this.props.customRule && <em className="mandatory">*</em>} + </label> + + {this.props.customRule ? ( + <span className="coding-rules-detail-custom-rule-key" title={this.props.customRule.key}> + {this.props.customRule.key} + </span> + ) : ( <input - autoFocus={true} - className="coding-rules-name-key" disabled={this.state.submitting} - id="coding-rules-custom-rule-creation-name" - onChange={this.handleNameChange} + id="coding-rules-custom-rule-creation-key" + onChange={this.handleKeyChange} required={true} type="text" - value={this.state.name} + value={this.state.key} /> - </td> - </tr> - ); - - renderKeyField = () => ( - <tr className="property"> - <th className="nowrap"> - <h3> - {translate('key')} {!this.props.customRule && <em className="mandatory">*</em>} - </h3> - </th> - <td> - {this.props.customRule ? ( - <span className="coding-rules-detail-custom-rule-key" title={this.props.customRule.key}> - {this.props.customRule.key} - </span> - ) : ( - <input - className="coding-rules-name-key" - disabled={this.state.submitting} - id="coding-rules-custom-rule-creation-key" - onChange={this.handleKeyChange} - required={true} - type="text" - value={this.state.key} - /> - )} - </td> - </tr> + )} + </div> ); renderDescriptionField = () => ( - <tr className="property"> - <th className="nowrap"> - <h3> - {translate('description')} <em className="mandatory">*</em> - </h3> - </th> - <td> - <textarea - className="coding-rules-markdown-description" - disabled={this.state.submitting} - id="coding-rules-custom-rule-creation-html-description" - onChange={this.handleDescriptionChange} - required={true} - rows={5} - value={this.state.description} - /> - <span className="text-right"> - <MarkdownTips /> - </span> - </td> - </tr> + <div className="modal-field"> + <label htmlFor="coding-rules-custom-rule-creation-html-description"> + {translate('description')} <em className="mandatory">*</em> + </label> + <textarea + disabled={this.state.submitting} + id="coding-rules-custom-rule-creation-html-description" + onChange={this.handleDescriptionChange} + required={true} + rows={5} + value={this.state.description} + /> + <MarkdownTips className="modal-field-descriptor text-right" /> + </div> ); renderTypeOption = ({ value }: { value: T.RuleType }) => { @@ -230,107 +214,99 @@ export default class CustomRuleFormModal extends React.PureComponent<Props, Stat }; renderTypeField = () => ( - <tr className="property"> - <th className="nowrap"> - <h3>{translate('type')}</h3> - </th> - <td> - <Select - className="input-medium" - clearable={false} - disabled={this.state.submitting} - onChange={this.handleTypeChange} - optionRenderer={this.renderTypeOption} - options={RULE_TYPES.map(type => ({ - label: translate('issue.type', type), - value: type - }))} - searchable={false} - value={this.state.type} - valueRenderer={this.renderTypeOption} - /> - </td> - </tr> + <div className="modal-field flex-1 spacer-right"> + <label htmlFor="coding-rules-custom-rule-type">{translate('type')}</label> + <Select + clearable={false} + disabled={this.state.submitting} + id="coding-rules-custom-rule-type" + onChange={this.handleTypeChange} + optionRenderer={this.renderTypeOption} + options={RULE_TYPES.map(type => ({ + label: translate('issue.type', type), + value: type + }))} + searchable={false} + value={this.state.type} + valueRenderer={this.renderTypeOption} + /> + </div> ); renderSeverityOption = ({ value }: { value: string }) => <SeverityHelper severity={value} />; renderSeverityField = () => ( - <tr className="property"> - <th className="nowrap"> - <h3>{translate('severity')}</h3> - </th> - <td> - <Select - className="input-medium" - clearable={false} - disabled={this.state.submitting} - onChange={this.handleSeverityChange} - optionRenderer={this.renderSeverityOption} - options={SEVERITIES.map(severity => ({ - label: translate('severity', severity), - value: severity - }))} - searchable={false} - value={this.state.severity} - valueRenderer={this.renderSeverityOption} - /> - </td> - </tr> + <div className="modal-field flex-1 spacer-right"> + <label htmlFor="coding-rules-custom-rule-severity">{translate('severity')}</label> + <Select + clearable={false} + disabled={this.state.submitting} + id="coding-rules-custom-rule-severity" + onChange={this.handleSeverityChange} + optionRenderer={this.renderSeverityOption} + options={SEVERITIES.map(severity => ({ + label: translate('severity', severity), + value: severity + }))} + searchable={false} + value={this.state.severity} + valueRenderer={this.renderSeverityOption} + /> + </div> ); renderStatusField = () => ( - <tr className="property"> - <th className="nowrap"> - <h3>{translate('coding_rules.filters.status')}</h3> - </th> - <td> - <Select - className="input-medium" - clearable={false} - disabled={this.state.submitting} - onChange={this.handleStatusChange} - options={RULE_STATUSES.map(status => ({ - label: translate('rules.status', status), - value: status - }))} - searchable={false} - value={this.state.status} - /> - </td> - </tr> + <div className="modal-field flex-1"> + <label htmlFor="coding-rules-custom-rule-status"> + {translate('coding_rules.filters.status')} + </label> + <Select + clearable={false} + disabled={this.state.submitting} + id="coding-rules-custom-rule-status" + onChange={this.handleStatusChange} + options={RULE_STATUSES.map(status => ({ + label: translate('rules.status', status), + value: status + }))} + searchable={false} + value={this.state.status} + /> + </div> ); renderParameterField = (param: T.RuleParameter) => ( - <tr className="property" key={param.key}> - <th className="nowrap"> - <h3>{param.key}</h3> - </th> - <td> - {param.type === 'TEXT' ? ( - <textarea - className="width100" - disabled={this.state.submitting} - name={param.key} - onChange={this.handleParameterChange} - placeholder={param.defaultValue} - rows={3} - value={this.state.params[param.key] || ''} - /> - ) : ( - <input - className="input-super-large" - disabled={this.state.submitting} - name={param.key} - onChange={this.handleParameterChange} - placeholder={param.defaultValue} - type="text" - value={this.state.params[param.key] || ''} - /> - )} - <div className="note" dangerouslySetInnerHTML={{ __html: param.htmlDesc || '' }} /> - </td> - </tr> + <div className="modal-field" key={param.key}> + <label className="capitalize" htmlFor={param.key}> + {param.key} + </label> + + {param.type === 'TEXT' ? ( + <textarea + disabled={this.state.submitting} + id={param.key} + name={param.key} + onChange={this.handleParameterChange} + placeholder={param.defaultValue} + rows={3} + value={this.state.params[param.key] || ''} + /> + ) : ( + <input + disabled={this.state.submitting} + id={param.key} + name={param.key} + onChange={this.handleParameterChange} + placeholder={param.defaultValue} + type="text" + value={this.state.params[param.key] || ''} + /> + )} + <div + className="modal-field-description" + dangerouslySetInnerHTML={{ __html: param.htmlDesc || '' }} + /> + </div> ); renderSubmitButton = () => { @@ -371,18 +347,17 @@ export default class CustomRuleFormModal extends React.PureComponent<Props, Stat {reactivating && ( <Alert variant="warning">{translate('coding_rules.reactivate.help')}</Alert> )} - <table> - <tbody> - {this.renderNameField()} - {this.renderKeyField()} - {this.renderDescriptionField()} - {/* do not allow to change the type of existing rule */} - {!customRule && this.renderTypeField()} - {this.renderSeverityField()} - {this.renderStatusField()} - {params.map(this.renderParameterField)} - </tbody> - </table> + + {this.renderNameField()} + {this.renderKeyField()} + <div className="display-flex-space-between"> + {/* do not allow to change the type of existing rule */} + {!customRule && this.renderTypeField()} + {this.renderSeverityField()} + {this.renderStatusField()} + </div> + {this.renderDescriptionField()} + {params.map(this.renderParameterField)} </div> <div className="modal-foot"> 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 39c11d5468a..966a96ca51c 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 @@ -104,7 +104,7 @@ export default class RuleDetailsCustomRules extends React.PureComponent<Props, S </td> <td className="coding-rules-detail-list-severity"> - <SeverityHelper severity={rule.severity} /> + <SeverityHelper className="display-flex-center" severity={rule.severity} /> </td> <td className="coding-rules-detail-list-parameters"> @@ -163,7 +163,7 @@ export default class RuleDetailsCustomRules extends React.PureComponent<Props, S </CustomRuleButton> )} - <DeferredSpinner loading={loading}> + <DeferredSpinner className="spacer-left" loading={loading}> {rules.length > 0 && ( <table className="coding-rules-detail-list" id="coding-rules-detail-custom-rules"> <tbody>{sortBy(rules, rule => rule.name).map(this.renderRule)}</tbody> 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 c7bc85d7817..0f22be47412 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 @@ -127,16 +127,16 @@ export default class RuleDetailsDescription extends React.PureComponent<Props, S renderForm = () => ( <div className="coding-rules-detail-extend-description-form"> - <table className="width100"> + <table className="width-100"> <tbody> <tr> - <td className="width100" colSpan={2}> + <td colSpan={2}> <textarea autoFocus={true} + className="width-100 little-spacer-bottom" id="coding-rules-detail-extend-description-text" onChange={this.handleDescriptionChange} rows={4} - style={{ width: '100%', marginBottom: 4 }} value={this.state.description} /> </td> 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 74cfa236e86..29f4de18cba 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 @@ -280,7 +280,7 @@ export default class RuleDetailsProfiles extends React.PureComponent<Props, Stat {activations.length > 0 && ( <table - className="coding-rules-detail-quality-profiles width100" + className="coding-rules-detail-quality-profiles width-100" 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/__tests__/ActivationFormModal-test.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/ActivationFormModal-test.tsx new file mode 100644 index 00000000000..5b9cd54183d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/ActivationFormModal-test.tsx @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import ActivationFormModal from '../ActivationFormModal'; +import { mockQualityProfile, mockRule } from '../../../../helpers/testMocks'; + +it('render correctly', () => { + expect( + shallow( + <ActivationFormModal + modalHeader="title" + onClose={jest.fn()} + onDone={jest.fn()} + organization="foo" + profiles={[mockQualityProfile()]} + rule={mockRule()} + /> + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/BulkChangeModal-test.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/BulkChangeModal-test.tsx new file mode 100644 index 00000000000..6174a899e02 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/BulkChangeModal-test.tsx @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import BulkChangeModal from '../BulkChangeModal'; +import { mockQualityProfile } from '../../../../helpers/testMocks'; +import { Query } from '../../query'; + +it('render correctly', () => { + expect( + shallow( + <BulkChangeModal + action="activate" + languages={{ js: { key: 'js', name: 'JavaScript' } }} + onClose={jest.fn()} + organization="foo" + profile={mockQualityProfile()} + query={{ languages: ['js'] } as Query} + referencedProfiles={{ foo: mockQualityProfile() }} + total={42} + /> + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/CustomRuleFormModal-test.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/CustomRuleFormModal-test.tsx new file mode 100644 index 00000000000..8677a879507 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/CustomRuleFormModal-test.tsx @@ -0,0 +1,39 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import CustomRuleFormModal from '../CustomRuleFormModal'; +import { mockRule } from '../../../../helpers/testMocks'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot(); +}); + +function shallowRender(props: Partial<CustomRuleFormModal['props']> = {}) { + return shallow( + <CustomRuleFormModal + onClose={jest.fn()} + onDone={jest.fn()} + organization={undefined} + templateRule={{ ...mockRule(), createdAt: 'date', repo: 'squid' }} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleListItem-test.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleListItem-test.tsx index 2b220a43753..3d7b44dcb09 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleListItem-test.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleListItem-test.tsx @@ -20,19 +20,7 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import RuleListItem from '../RuleListItem'; -import { mockEvent } from '../../../../helpers/testMocks'; - -const rule: T.Rule = { - key: 'foo', - lang: 'js', - langName: 'JavaScript', - name: 'Use foo', - severity: 'MAJOR', - status: 'READY', - sysTags: ['a', 'b'], - tags: ['x'], - type: 'CODE_SMELL' -}; +import { mockEvent, mockRule } from '../../../../helpers/testMocks'; it('should render', () => { expect(shallowRender()).toMatchSnapshot(); @@ -42,7 +30,7 @@ it('should open rule', () => { const onOpen = jest.fn(); const wrapper = shallowRender({ onOpen }); wrapper.find('Link').prop<Function>('onClick')(mockEvent({ button: 0 })); - expect(onOpen).toBeCalledWith('foo'); + expect(onOpen).toBeCalledWith('javascript:S1067'); }); function shallowRender(props?: Partial<RuleListItem['props']>) { @@ -53,7 +41,7 @@ function shallowRender(props?: Partial<RuleListItem['props']>) { onFilterChange={jest.fn()} onOpen={jest.fn()} organization="org" - rule={rule} + rule={mockRule()} selected={false} {...props} /> diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/ActivationFormModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/ActivationFormModal-test.tsx.snap new file mode 100644 index 00000000000..2225b883507 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/ActivationFormModal-test.tsx.snap @@ -0,0 +1,101 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`render correctly 1`] = ` +<Modal + contentLabel="title" + onRequestClose={[MockFunction]} + size="small" +> + <form + onSubmit={[Function]} + > + <div + className="modal-head" + > + <h2> + title + </h2> + </div> + <div + className="modal-body" + > + <Alert + variant="info" + > + coding_rules.active_in_all_profiles + </Alert> + <div + className="modal-field" + > + <label> + coding_rules.quality_profile + </label> + <Select + className="js-profile" + clearable={false} + disabled={false} + onChange={[Function]} + options={Array []} + value="" + /> + </div> + <div + className="modal-field" + > + <label> + severity + </label> + <Select + className="js-severity" + clearable={false} + disabled={false} + onChange={[Function]} + optionRenderer={[Function]} + options={ + Array [ + Object { + "label": "severity.BLOCKER", + "value": "BLOCKER", + }, + Object { + "label": "severity.CRITICAL", + "value": "CRITICAL", + }, + Object { + "label": "severity.MAJOR", + "value": "MAJOR", + }, + Object { + "label": "severity.MINOR", + "value": "MINOR", + }, + Object { + "label": "severity.INFO", + "value": "INFO", + }, + ] + } + searchable={false} + value="MAJOR" + valueRenderer={[Function]} + /> + </div> + </div> + <footer + className="modal-foot" + > + <SubmitButton + disabled={true} + > + coding_rules.activate + </SubmitButton> + <ResetButtonLink + disabled={false} + onClick={[MockFunction]} + > + cancel + </ResetButtonLink> + </footer> + </form> +</Modal> +`; diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap new file mode 100644 index 00000000000..fed86bf741a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap @@ -0,0 +1,56 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`render correctly 1`] = ` +<Modal + contentLabel="coding_rules.activate_in_quality_profile (42 coding_rules._rules)" + onRequestClose={[MockFunction]} + size="small" +> + <form + onSubmit={[Function]} + > + <header + className="modal-head" + > + <h2> + coding_rules.activate_in_quality_profile (42 coding_rules._rules) + </h2> + </header> + <div + className="modal-body" + > + <div + className="modal-field" + > + <h3> + <label + htmlFor="coding-rules-bulk-change-profile" + > + coding_rules.activate_in + </label> + </h3> + <span> + name + — + are_you_sure + </span> + </div> + </div> + <footer + className="modal-foot" + > + <SubmitButton + disabled={false} + id="coding-rules-submit-bulk-change" + > + apply + </SubmitButton> + <ResetButtonLink + onClick={[MockFunction]} + > + cancel + </ResetButtonLink> + </footer> + </form> +</Modal> +`; diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/CustomRuleFormModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/CustomRuleFormModal-test.tsx.snap new file mode 100644 index 00000000000..71f42992ddd --- /dev/null +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/CustomRuleFormModal-test.tsx.snap @@ -0,0 +1,237 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<Modal + contentLabel="coding_rules.create_custom_rule" + onRequestClose={[MockFunction]} +> + <form + onSubmit={[Function]} + > + <div + className="modal-head" + > + <h2> + coding_rules.create_custom_rule + </h2> + </div> + <div + className="modal-body modal-container" + > + <div + className="modal-field" + > + <label + htmlFor="coding-rules-custom-rule-creation-name" + > + name + + <em + className="mandatory" + > + * + </em> + </label> + <input + autoFocus={true} + disabled={false} + id="coding-rules-custom-rule-creation-name" + onChange={[Function]} + required={true} + type="text" + value="" + /> + </div> + <div + className="modal-field" + > + <label + htmlFor="coding-rules-custom-rule-creation-key" + > + key + + <em + className="mandatory" + > + * + </em> + </label> + <input + disabled={false} + id="coding-rules-custom-rule-creation-key" + onChange={[Function]} + required={true} + type="text" + value="" + /> + </div> + <div + className="display-flex-space-between" + > + <div + className="modal-field flex-1 spacer-right" + > + <label + htmlFor="coding-rules-custom-rule-type" + > + type + </label> + <Select + clearable={false} + disabled={false} + id="coding-rules-custom-rule-type" + onChange={[Function]} + optionRenderer={[Function]} + options={ + Array [ + Object { + "label": "issue.type.BUG", + "value": "BUG", + }, + Object { + "label": "issue.type.VULNERABILITY", + "value": "VULNERABILITY", + }, + Object { + "label": "issue.type.CODE_SMELL", + "value": "CODE_SMELL", + }, + Object { + "label": "issue.type.SECURITY_HOTSPOT", + "value": "SECURITY_HOTSPOT", + }, + Object { + "label": "issue.type.UNKNOWN", + "value": "UNKNOWN", + }, + ] + } + searchable={false} + value="CODE_SMELL" + valueRenderer={[Function]} + /> + </div> + <div + className="modal-field flex-1 spacer-right" + > + <label + htmlFor="coding-rules-custom-rule-severity" + > + severity + </label> + <Select + clearable={false} + disabled={false} + id="coding-rules-custom-rule-severity" + onChange={[Function]} + optionRenderer={[Function]} + options={ + Array [ + Object { + "label": "severity.BLOCKER", + "value": "BLOCKER", + }, + Object { + "label": "severity.CRITICAL", + "value": "CRITICAL", + }, + Object { + "label": "severity.MAJOR", + "value": "MAJOR", + }, + Object { + "label": "severity.MINOR", + "value": "MINOR", + }, + Object { + "label": "severity.INFO", + "value": "INFO", + }, + ] + } + searchable={false} + value="MAJOR" + valueRenderer={[Function]} + /> + </div> + <div + className="modal-field flex-1" + > + <label + htmlFor="coding-rules-custom-rule-status" + > + coding_rules.filters.status + </label> + <Select + clearable={false} + disabled={false} + id="coding-rules-custom-rule-status" + onChange={[Function]} + options={ + Array [ + Object { + "label": "rules.status.READY", + "value": "READY", + }, + Object { + "label": "rules.status.BETA", + "value": "BETA", + }, + Object { + "label": "rules.status.DEPRECATED", + "value": "DEPRECATED", + }, + ] + } + searchable={false} + value="READY" + /> + </div> + </div> + <div + className="modal-field" + > + <label + htmlFor="coding-rules-custom-rule-creation-html-description" + > + description + + <em + className="mandatory" + > + * + </em> + </label> + <textarea + disabled={false} + id="coding-rules-custom-rule-creation-html-description" + onChange={[Function]} + required={true} + rows={5} + value="" + /> + <MarkdownTips + className="modal-field-descriptor text-right" + /> + </div> + </div> + <div + className="modal-foot" + > + <SubmitButton + disabled={false} + id="coding-rules-custom-rule-creation-create" + > + create + </SubmitButton> + <ResetButtonLink + disabled={false} + id="coding-rules-custom-rule-creation-cancel" + onClick={[MockFunction]} + > + cancel + </ResetButtonLink> + </div> + </form> +</Modal> +`; diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleListItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleListItem-test.tsx.snap index 459a8165037..e1b94a8164a 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleListItem-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleListItem-test.tsx.snap @@ -3,7 +3,7 @@ exports[`should render 1`] = ` <div className="coding-rule" - data-rule="foo" + data-rule="javascript:S1067" > <table className="coding-rule-table" @@ -23,8 +23,8 @@ exports[`should render 1`] = ` Object { "pathname": "/organizations/org/rules", "query": Object { - "open": "foo", - "rule_key": "foo", + "open": "javascript:S1067", + "rule_key": "javascript:S1067", }, } } @@ -72,7 +72,7 @@ exports[`should render 1`] = ` onFilterChange={[MockFunction]} rule={ Object { - "key": "foo", + "key": "javascript:S1067", "lang": "js", "langName": "JavaScript", "name": "Use foo", diff --git a/server/sonar-web/src/main/js/apps/coding-rules/styles.css b/server/sonar-web/src/main/js/apps/coding-rules/styles.css index ca7a4fdde40..8450e0dab94 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/styles.css +++ b/server/sonar-web/src/main/js/apps/coding-rules/styles.css @@ -201,15 +201,6 @@ margin-left: 10px; } -input.coding-rules-name-key { - width: 100%; -} - -textarea.coding-rules-markdown-description { - width: 100%; - margin-bottom: 4px; -} - .coding-rules-most-violated-projects td { border-top-color: transparent; } diff --git a/server/sonar-web/src/main/js/apps/create/components/UpgradeOrganizationModal.tsx b/server/sonar-web/src/main/js/apps/create/components/UpgradeOrganizationModal.tsx index dfaca7bc0f0..27467f3420f 100644 --- a/server/sonar-web/src/main/js/apps/create/components/UpgradeOrganizationModal.tsx +++ b/server/sonar-web/src/main/js/apps/create/components/UpgradeOrganizationModal.tsx @@ -72,10 +72,10 @@ export default class UpgradeOrganizationModal extends React.PureComponent<Props, return ( <Modal contentLabel={header} - medium={true} noBackdrop={this.props.insideModal} onRequestClose={this.props.onClose} - shouldCloseOnOverlayClick={false}> + shouldCloseOnOverlayClick={false} + size={'medium'}> <div className="modal-head"> <h2>{header}</h2> </div> diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/UpgradeOrganizationModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/UpgradeOrganizationModal-test.tsx.snap index 3043cc08b5f..4d294fc1497 100644 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/UpgradeOrganizationModal-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/UpgradeOrganizationModal-test.tsx.snap @@ -3,9 +3,9 @@ exports[`should render correctly 1`] = ` <Modal contentLabel="billing.upgrade_box.upgrade_to_paid_plan" - medium={true} onRequestClose={[MockFunction]} shouldCloseOnOverlayClick={false} + size="medium" > <div className="modal-head" diff --git a/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx b/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx index bce0bcb2c21..3a40718de56 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx @@ -77,12 +77,17 @@ export default class AutoOrganizationCreate extends React.PureComponent<Props, S }; handleCreateOrganization = () => { - const { organization } = this.props; + const { almApplication, almOrganization, organization } = this.props; if (!organization) { return Promise.reject(); } return this.props.createOrganization({ ...organization, + alm: { + key: almApplication.key, + membersSync: true, + url: almOrganization.almUrl + }, installationId: this.props.almInstallId }); }; @@ -169,10 +174,13 @@ export default class AutoOrganizationCreate extends React.PureComponent<Props, S )} </p> <a - href={getAlmMembersUrl(almOrganization.key, almOrganization.almUrl)} + href={getAlmMembersUrl(almApplication.key, almOrganization.almUrl)} rel="noopener noreferrer" target="_blank"> - {translate('onboarding.import_organization.see_who_has_access')} + {translateWithParameters( + 'organization.members.see_all_members_on_x', + translate(almKey) + )} </a> </Alert> } diff --git a/server/sonar-web/src/main/js/apps/create/organization/RemoteOrganizationChoose.tsx b/server/sonar-web/src/main/js/apps/create/organization/RemoteOrganizationChoose.tsx index 4c086a58779..33d9fcc8d79 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/RemoteOrganizationChoose.tsx +++ b/server/sonar-web/src/main/js/apps/create/organization/RemoteOrganizationChoose.tsx @@ -168,7 +168,7 @@ export class RemoteOrganizationChoose extends React.PureComponent<Props & WithRo </div> <form className="big-spacer-top big-spacer-bottom" onSubmit={this.handleSubmit}> <div className="form-field abs-width-400"> - <label htmlFor="select-unbound-installation"> + <label className="text-normal" htmlFor="select-unbound-installation"> {translateWithParameters( 'onboarding.import_organization.choose_unbound_installation_x', translate(sanitizeAlmId(almApplication.key)) diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/AutoOrganizationCreate-test.tsx b/server/sonar-web/src/main/js/apps/create/organization/__tests__/AutoOrganizationCreate-test.tsx index 8ac2a9e1a07..b3fe16d362c 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/AutoOrganizationCreate-test.tsx +++ b/server/sonar-web/src/main/js/apps/create/organization/__tests__/AutoOrganizationCreate-test.tsx @@ -22,7 +22,7 @@ import { shallow } from 'enzyme'; import AutoOrganizationCreate from '../AutoOrganizationCreate'; import { Step } from '../utils'; import { bindAlmOrganization } from '../../../../api/alm-integration'; -import { mockAlmOrganization } from '../../../../helpers/testMocks'; +import { mockAlmOrganization, mockAlmApplication } from '../../../../helpers/testMocks'; import { waitAndUpdate, click } from '../../../../helpers/testUtils'; jest.mock('../../../../api/alm-integration', () => ({ @@ -34,7 +34,14 @@ const organization = mockAlmOrganization(); it('should render prefilled and create org', async () => { const createOrganization = jest.fn().mockResolvedValue({ key: 'foo' }); const handleOrgDetailsFinish = jest.fn(); - const wrapper = shallowRender({ createOrganization, handleOrgDetailsFinish }); + const almApplication = mockAlmApplication({ key: 'github' }); + const almOrganization = mockAlmOrganization({ almUrl: 'http://github.com/thing' }); + const wrapper = shallowRender({ + almApplication, + almOrganization, + createOrganization, + handleOrgDetailsFinish + }); expect(wrapper).toMatchSnapshot(); @@ -44,7 +51,13 @@ it('should render prefilled and create org', async () => { wrapper.setProps({ organization }); wrapper.find('PlanStep').prop<Function>('createOrganization')(); - expect(createOrganization).toBeCalledWith({ ...organization, installationId: 'id-foo' }); + + const alm = { + key: 'github', + membersSync: true, + url: 'http://github.com/thing' + }; + expect(createOrganization).toBeCalledWith({ ...organization, alm, installationId: 'id-foo' }); }); it('should allow to cancel org import', () => { @@ -86,13 +99,7 @@ it('should bind existing organization', async () => { function shallowRender(props: Partial<AutoOrganizationCreate['props']> = {}) { return shallow( <AutoOrganizationCreate - almApplication={{ - backgroundColor: '#0052CC', - iconPath: '"/static/authbitbucket/bitbucket.svg"', - installationUrl: 'https://bitbucket.org/install/app', - key: 'bitbucket', - name: 'BitBucket' - }} + almApplication={mockAlmApplication()} almInstallId="id-foo" almOrganization={{ ...organization, personal: false }} createOrganization={jest.fn()} diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AutoOrganizationCreate-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AutoOrganizationCreate-test.tsx.snap index d16fbc70263..ca0868ae105 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AutoOrganizationCreate-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AutoOrganizationCreate-test.tsx.snap @@ -121,7 +121,7 @@ exports[`should render prefilled and create org 1`] = ` "avatar": <img alt="BitBucket" className="little-spacer-left" - src="/images/sonarcloud/bitbucket.svg" + src="/images/sonarcloud/github.svg" width={16} />, "name": <strong> @@ -144,21 +144,21 @@ exports[`should render prefilled and create org 1`] = ` variant="info" > <p> - onboarding.import_organization.members_sync_info_x.organization.bitbucket.foo.bitbucket + onboarding.import_organization.members_sync_info_x.organization.github.foo.github </p> <a - href="https://github.com/foo/profile/members" + href="http://github.com/orgs/thing/people" rel="noopener noreferrer" target="_blank" > - onboarding.import_organization.see_who_has_access + organization.members.see_all_members_on_x.github </a> </Alert> } onContinue={[MockFunction]} organization={ Object { - "almUrl": "https://github.com/foo", + "almUrl": "http://github.com/thing", "avatar": "http://example.com/avatar", "description": "description-foo", "key": "foo", @@ -178,13 +178,13 @@ exports[`should render prefilled and create org 1`] = ` "backgroundColor": "#0052CC", "iconPath": "\\"/static/authbitbucket/bitbucket.svg\\"", "installationUrl": "https://bitbucket.org/install/app", - "key": "bitbucket", + "key": "github", "name": "BitBucket", } } almOrganization={ Object { - "almUrl": "https://github.com/foo", + "almUrl": "http://github.com/thing", "avatar": "http://example.com/avatar", "description": "description-foo", "key": "foo", diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/RemoteOrganizationChoose-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/RemoteOrganizationChoose-test.tsx.snap index 8cd6afa0fd3..cae179cdd68 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/RemoteOrganizationChoose-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/RemoteOrganizationChoose-test.tsx.snap @@ -124,6 +124,7 @@ exports[`should display unbound installations 1`] = ` className="form-field abs-width-400" > <label + className="text-normal" htmlFor="select-unbound-installation" > onboarding.import_organization.choose_unbound_installation_x.github diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/actions-test.ts b/server/sonar-web/src/main/js/apps/create/organization/__tests__/actions-test.ts index 26800ae126a..f95556451f8 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/actions-test.ts +++ b/server/sonar-web/src/main/js/apps/create/organization/__tests__/actions-test.ts @@ -51,9 +51,13 @@ describe('#createOrganization', () => { }); it('should create and sync members', async () => { - const org = mockOrganizationWithAlm({}, { membersSync: true }); + const { alm, ...org } = mockOrganizationWithAlm( + {}, + { key: 'github', membersSync: true, url: 'https://github.com/foo' } + ); + (createOrganization as jest.Mock).mockResolvedValueOnce(org); - const promise = actions.createOrganization(org)(dispatch); + const promise = actions.createOrganization({ alm, ...org })(dispatch); expect(createOrganization).toHaveBeenCalledWith(org); await promise; diff --git a/server/sonar-web/src/main/js/apps/create/organization/actions.ts b/server/sonar-web/src/main/js/apps/create/organization/actions.ts index 400de6cdf61..38b90fff706 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/actions.ts +++ b/server/sonar-web/src/main/js/apps/create/organization/actions.ts @@ -21,17 +21,21 @@ import { Dispatch } from 'redux'; import { bindAlmOrganization } from '../../../api/alm-integration'; import * as api from '../../../api/organizations'; import * as actions from '../../../store/organizations'; +import { isGithub } from '../../../helpers/almIntegrations'; -export function createOrganization(organization: T.Organization & { installationId?: string }) { +export function createOrganization({ + alm, + ...organization +}: T.Organization & { installationId?: string }) { return (dispatch: Dispatch) => { return api .createOrganization({ ...organization, name: organization.name || organization.key }) - .then((organization: T.Organization) => { - dispatch(actions.createOrganization(organization)); - if (organization.alm && organization.alm.membersSync) { - api.syncMembers(organization.key); + .then((newOrganization: T.Organization) => { + dispatch(actions.createOrganization({ ...newOrganization, alm })); + if (alm && alm.membersSync && isGithub(alm.key)) { + api.syncMembers(newOrganization.key); } - return organization.key; + return newOrganization.key; }); }; } 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 261872e75ae..053b0789166 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 @@ -18,13 +18,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { getAllMetrics } from '../../../api/metrics'; import DeferredSpinner from '../../../components/common/DeferredSpinner'; import Select from '../../../components/controls/Select'; import SimpleModal from '../../../components/controls/SimpleModal'; +import { Alert } from '../../../components/ui/Alert'; import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; +import { getAllMetrics } from '../../../api/metrics'; import { translate } from '../../../helpers/l10n'; -import { Alert } from '../../../components/ui/Alert'; interface Props { confirmButtonText: string; @@ -145,7 +145,8 @@ export default class Form extends React.PureComponent<Props, State> { <SimpleModal header={this.props.header} onClose={this.props.onClose} - onSubmit={this.handleSubmit}> + onSubmit={this.handleSubmit} + size="small"> {({ onCloseClick, onFormSubmit, submitting }) => ( <form onSubmit={onFormSubmit}> <header className="modal-head"> 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 91da25f4c4b..aa237802219 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 @@ -4,6 +4,7 @@ exports[`should render form 1`] = ` <Modal contentLabel="header" onRequestClose={[MockFunction]} + size="small" > <form onSubmit={[Function]} @@ -105,6 +106,7 @@ exports[`should render form 2`] = ` <Modal contentLabel="header" onRequestClose={[MockFunction]} + size="small" > <form onSubmit={[Function]} 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 ad67fa6bc80..ffc3d920734 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 @@ -20,9 +20,9 @@ import * as React from 'react'; 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'; +import { translate } from '../../../helpers/l10n'; export interface MetricProps { description: string; @@ -98,7 +98,8 @@ export default class Form extends React.PureComponent<Props, State> { <SimpleModal header={this.props.header} onClose={this.props.onClose} - onSubmit={this.handleSubmit}> + onSubmit={this.handleSubmit} + size="small"> {({ onCloseClick, onFormSubmit, submitting }) => ( <form onSubmit={onFormSubmit}> <header className="modal-head"> 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 69ff6188f6b..692174076ae 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 @@ -4,6 +4,7 @@ exports[`should render form 1`] = ` <Modal contentLabel="header" onRequestClose={[MockFunction]} + size="small" > <form onSubmit={[Function]} 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 73e2c0ee5f7..3f5d72afe08 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 @@ -64,7 +64,8 @@ export default class Form extends React.PureComponent<Props, State> { <SimpleModal header={this.props.header} onClose={this.props.onClose} - onSubmit={this.handleSubmit}> + onSubmit={this.handleSubmit} + size="small"> {({ onCloseClick, onFormSubmit, submitting }) => ( <form onSubmit={onFormSubmit}> <header className="modal-head"> 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 2b0954e09fb..58f9268cf6c 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 @@ -4,6 +4,7 @@ exports[`should render form 1`] = ` <Modal contentLabel="header" onRequestClose={[MockFunction]} + size="small" > <form onSubmit={[Function]} diff --git a/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx b/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx index 361fb238886..5082742f980 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx @@ -18,23 +18,25 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import * as classNames from 'classnames'; import { FormattedMessage } from 'react-intl'; import { pickBy, sortBy } from 'lodash'; import { searchAssignees } from '../utils'; -import { searchIssueTags, bulkChangeIssues } from '../../../api/issues'; -import throwGlobalError from '../../../app/utils/throwGlobalError'; -import MarkdownTips from '../../../components/common/MarkdownTips'; -import SearchSelect from '../../../components/controls/SearchSelect'; +import Avatar from '../../../components/ui/Avatar'; import Checkbox from '../../../components/controls/Checkbox'; +import HelpTooltip from '../../../components/controls/HelpTooltip'; +import IssueTypeIcon from '../../../components/ui/IssueTypeIcon'; +import MarkdownTips from '../../../components/common/MarkdownTips'; import Modal from '../../../components/controls/Modal'; +import Radio from '../../../components/controls/Radio'; +import SearchSelect from '../../../components/controls/SearchSelect'; import Select from '../../../components/controls/Select'; -import HelpTooltip from '../../../components/controls/HelpTooltip'; import SeverityHelper from '../../../components/shared/SeverityHelper'; -import Avatar from '../../../components/ui/Avatar'; +import throwGlobalError from '../../../app/utils/throwGlobalError'; +import { Alert } from '../../../components/ui/Alert'; +import { searchIssueTags, bulkChangeIssues } from '../../../api/issues'; import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons'; -import IssueTypeIcon from '../../../components/ui/IssueTypeIcon'; import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { Alert } from '../../../components/ui/Alert'; import { isLoggedIn } from '../../../helpers/users'; interface AssigneeOption { @@ -188,10 +190,12 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> { } }; - handleFieldChange = (field: 'comment' | 'transition') => ( - event: React.SyntheticEvent<HTMLInputElement | HTMLTextAreaElement> - ) => { - this.setState<keyof FormFields>({ [field]: event.currentTarget.value }); + handleRadioTransitionChange = (transition: string) => { + this.setState({ transition }); + }; + + handleCommentChange = (event: React.SyntheticEvent<HTMLTextAreaElement>) => { + this.setState({ comment: event.currentTarget.value }); }; handleSelectFieldChange = (field: 'severity' | 'type') => (data: { value: string } | null) => { @@ -269,14 +273,6 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> { </div> ); - renderCheckbox = (field: keyof FormFields, id?: string) => ( - <Checkbox - checked={this.state[field] !== undefined} - id={id} - onCheck={this.handleFieldCheck(field)} - /> - ); - renderAffected = (affected: number) => ( <div className="pull-right note"> ({translateWithParameters('issue_bulk_change.x_issues', affected)}) @@ -316,6 +312,7 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> { const input = ( <AssigneeSelect + className="input-super-large" clearable={true} defaultOptions={this.getDefaultAssignee()} onSearch={this.handleAssigneeSearch} @@ -348,6 +345,7 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> { const input = ( <Select + className="input-super-large" clearable={true} onChange={this.handleSelectFieldChange('type')} optionRenderer={optionRenderer} @@ -376,6 +374,7 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> { const input = ( <Select + className="input-super-large" clearable={true} onChange={this.handleSelectFieldChange('severity')} optionRenderer={(option: { value: string }) => <SeverityHelper severity={option.value} />} @@ -404,6 +403,7 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> { const input = ( <TagSelect canCreate={allowCreate} + className="input-super-large" clearable={true} defaultOptions={this.state.initialTags} minimumQueryLength={0} @@ -431,22 +431,16 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> { <div className="modal-field"> <label>{translate('issue.transition')}</label> {transitions.map(transition => ( - <span className="clearfix" key={transition.transition}> - <input + <span + className="bulk-change-radio-button display-flex-center display-flex-space-between" + key={transition.transition}> + <Radio checked={this.state.transition === transition.transition} - id={`transition-${transition.transition}`} - name="do_transition.transition" - onChange={this.handleFieldChange('transition')} - type="radio" - value={transition.transition} - /> - <label - htmlFor={`transition-${transition.transition}`} - style={{ float: 'none', display: 'inline', left: 0, cursor: 'pointer' }}> + onCheck={this.handleRadioTransitionChange} + value={transition.transition}> {translate('issue.transition', transition.transition)} - </label> + </Radio> {this.renderAffected(transition.count)} - <br /> </span> ))} </div> @@ -469,27 +463,26 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> { overlay={translate('issue_bulk_change.comment.help')} /> </label> - <div> - <textarea - id="comment" - onChange={this.handleFieldChange('comment')} - rows={4} - style={{ width: '100%' }} - value={this.state.comment || ''} - /> - </div> - <div className="pull-right"> - <MarkdownTips /> - </div> + <textarea + id="comment" + onChange={this.handleCommentChange} + rows={4} + value={this.state.comment || ''} + /> + <MarkdownTips className="modal-field-descriptor text-right" /> </div> ); }; renderNotificationsField = () => ( - <div className="modal-field"> - <label htmlFor="send-notifications">{translate('issue.send_notifications')}</label> - {this.renderCheckbox('notifications', 'send-notifications')} - </div> + <Checkbox + checked={this.state.notifications !== undefined} + className="display-inline-block spacer-top" + id="send-notifications" + onCheck={this.handleFieldCheck('notifications')} + right={true}> + <strong className="little-spacer-right">{translate('issue.send_notifications')}</strong> + </Checkbox> ); renderForm = () => { @@ -503,7 +496,7 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> { <h2>{translateWithParameters('issue_bulk_change.form.title', issues.length)}</h2> </div> - <div className="modal-body"> + <div className={classNames('modal-body', { 'modal-container': limitReached })}> {limitReached && ( <Alert variant="warning"> <FormattedMessage @@ -540,7 +533,7 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> { render() { return ( - <Modal contentLabel="modal" onRequestClose={this.props.onClose}> + <Modal contentLabel="modal" onRequestClose={this.props.onClose} size={'small'}> {this.state.loading ? this.renderLoading() : this.renderForm()} </Modal> ); diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap index e45da7a29a6..a15fbc837cb 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap @@ -4,6 +4,7 @@ exports[`should display error message when no issues available 1`] = ` <Modal contentLabel="modal" onRequestClose={[Function]} + size="small" > <form id="bulk-change-form" @@ -48,6 +49,7 @@ exports[`should display form when issues are present 1`] = ` <Modal contentLabel="modal" onRequestClose={[Function]} + size="small" > <form id="bulk-change-form" @@ -63,21 +65,20 @@ exports[`should display form when issues are present 1`] = ` <div className="modal-body" > - <div - className="modal-field" + <Checkbox + checked={false} + className="display-inline-block spacer-top" + id="send-notifications" + onCheck={[Function]} + right={true} + thirdState={false} > - <label - htmlFor="send-notifications" + <strong + className="little-spacer-right" > issue.send_notifications - </label> - <Checkbox - checked={false} - id="send-notifications" - onCheck={[Function]} - thirdState={false} - /> - </div> + </strong> + </Checkbox> </div> <div className="modal-foot" diff --git a/server/sonar-web/src/main/js/apps/issues/styles.css b/server/sonar-web/src/main/js/apps/issues/styles.css index ff12f912ecc..9964171042b 100644 --- a/server/sonar-web/src/main/js/apps/issues/styles.css +++ b/server/sonar-web/src/main/js/apps/issues/styles.css @@ -270,3 +270,12 @@ width: auto; margin-right: 4px; } + +.bulk-change-radio-button { + margin: 0 calc(- var(--gridSize) / 2); + padding: 0 calc(var(--gridSize) / 2); +} + +.bulk-change-radio-button:hover { + background-color: var(--barBackgroundColor); +} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/AddMemberForm.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/AddMemberForm.tsx index 8cbfe2f6f20..a60df045b64 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/AddMemberForm.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/AddMemberForm.tsx @@ -77,7 +77,7 @@ export default class AddMemberForm extends React.PureComponent<Props, State> { </header> <form onSubmit={this.handleSubmit}> <div className="modal-body"> - <div className="modal-large-field"> + <div className="modal-field"> <label>{translate('users.search_description')}</label> <UsersSelectSearch autoFocus={true} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/ManageMemberGroupsForm.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/ManageMemberGroupsForm.tsx index 26d23cbb965..1bc300691ad 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/ManageMemberGroupsForm.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/ManageMemberGroupsForm.tsx @@ -18,19 +18,24 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { keyBy, pickBy } from 'lodash'; -import { getUserGroups, UserGroup } from '../../api/users'; -import Modal from '../../components/controls/Modal'; -import { translate, translateWithParameters } from '../../helpers/l10n'; +import { keyBy, pickBy, some } from 'lodash'; import OrganizationGroupCheckbox from '../organizations/components/OrganizationGroupCheckbox'; +import SimpleModal from '../../components/controls/SimpleModal'; import { SubmitButton, ResetButtonLink } from '../../components/ui/buttons'; +import { getUserGroups, UserGroup } from '../../api/users'; +import { translate, translateWithParameters } from '../../helpers/l10n'; +import DeferredSpinner from '../../components/common/DeferredSpinner'; interface Props { onClose: () => void; member: T.OrganizationMember; organization: T.Organization; organizationGroups: T.Group[]; - updateMemberGroups: (member: T.OrganizationMember, add: string[], remove: string[]) => void; + updateMemberGroups: ( + member: T.OrganizationMember, + add: string[], + remove: string[] + ) => Promise<void>; } interface State { @@ -81,7 +86,7 @@ export default class ManageMemberGroupsForm extends React.PureComponent<Props, S onCheck = (groupName: string, checked: boolean) => { this.setState((prevState: State) => { - const userGroups = prevState.userGroups || {}; + const { userGroups = {} } = prevState; const group = userGroups[groupName] || {}; let status = ''; if (group.selected && !checked) { @@ -93,53 +98,62 @@ export default class ManageMemberGroupsForm extends React.PureComponent<Props, S }); }; - handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { - event.preventDefault(); - this.props.updateMemberGroups( - this.props.member, - Object.keys(pickBy(this.state.userGroups, group => group.status === 'add')), - Object.keys(pickBy(this.state.userGroups, group => group.status === 'remove')) - ); - this.props.onClose(); + handleSubmit = () => { + return this.props + .updateMemberGroups( + this.props.member, + Object.keys(pickBy(this.state.userGroups, group => group.status === 'add')), + Object.keys(pickBy(this.state.userGroups, group => group.status === 'remove')) + ) + .then(this.props.onClose); }; render() { + const { loading, userGroups = {} } = this.state; const header = translate('organization.members.manage_groups'); + const hasChanges = some(userGroups, group => group.status !== undefined); return ( - <Modal contentLabel={header} onRequestClose={this.props.onClose}> - <header className="modal-head"> - <h2>{header}</h2> - </header> - <form onSubmit={this.handleSubmit}> - <div className="modal-body modal-container"> - <strong> - {translateWithParameters( - 'organization.members.members_groups', - this.props.member.name + <SimpleModal header={header} onClose={this.props.onClose} onSubmit={this.handleSubmit}> + {({ onCloseClick, onFormSubmit, submitting }) => ( + <form onSubmit={onFormSubmit}> + <header className="modal-head"> + <h2>{header}</h2> + </header> + <div className="modal-body modal-container"> + <p> + <strong> + {translateWithParameters( + 'organization.members.members_groups', + this.props.member.name + )} + </strong> + </p> + {loading ? ( + <DeferredSpinner className="spacer-top" /> + ) : ( + <ul className="list-spaced"> + {this.props.organizationGroups.map(group => ( + <OrganizationGroupCheckbox + checked={this.isGroupSelected(group.name)} + group={group} + key={group.id} + onCheck={this.onCheck} + /> + ))} + </ul> )} - </strong>{' '} - {this.state.loading && <i className="spinner" />} - {!this.state.loading && ( - <ul className="list-spaced"> - {this.props.organizationGroups.map(group => ( - <OrganizationGroupCheckbox - checked={this.isGroupSelected(group.name)} - group={group} - key={group.id} - onCheck={this.onCheck} - /> - ))} - </ul> - )} - </div> - <footer className="modal-foot"> - <div> - <SubmitButton>{translate('save')}</SubmitButton> - <ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink> </div> - </footer> - </form> - </Modal> + + <footer className="modal-foot"> + <DeferredSpinner className="spacer-right" loading={submitting} /> + <SubmitButton disabled={submitting || !hasChanges}>{translate('save')}</SubmitButton> + <ResetButtonLink disabled={submitting} onClick={onCloseClick}> + {translate('cancel')} + </ResetButtonLink> + </footer> + </form> + )} + </SimpleModal> ); } } diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/MembersList.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/MembersList.tsx index 437b0ed61b9..4cc845a58ce 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/MembersList.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/MembersList.tsx @@ -23,20 +23,21 @@ import MembersListItem from './MembersListItem'; import { translate } from '../../helpers/l10n'; interface Props { + currentUser: T.LoggedInUser; members: T.OrganizationMember[]; organizationGroups: T.Group[]; organization: T.Organization; - removeMember: (member: T.OrganizationMember) => void; + removeMember?: (member: T.OrganizationMember) => void; updateMemberGroups: ( member: T.OrganizationMember, add: Array<string>, remove: Array<string> - ) => void; + ) => Promise<void>; } export default class MembersList extends React.PureComponent<Props> { render() { - const { members } = this.props; + const { currentUser, members } = this.props; if (!members.length) { return <div className="note">{translate('no_results')}</div>; @@ -53,7 +54,9 @@ export default class MembersList extends React.PureComponent<Props> { member={member} organization={this.props.organization} organizationGroups={this.props.organizationGroups} - removeMember={this.props.removeMember} + removeMember={ + currentUser.login !== member.login ? this.props.removeMember : undefined + } updateMemberGroups={this.props.updateMemberGroups} /> ))} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/MembersListHeader.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/MembersListHeader.tsx index 11a052e4462..beb8fe92be5 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/MembersListHeader.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/MembersListHeader.tsx @@ -52,7 +52,7 @@ export default function MembersListHeader({ <HelpTooltip className="spacer-left" overlay={ - <div className="abs-width-300 markdown cut-margins"> + <div className="abs-width-300 markdown cut-margins"> <p> {translate( 'organization.members.auto_sync_total_help', diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/MembersListItem.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/MembersListItem.tsx index 143b7a549b3..4859e2f2ee5 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/MembersListItem.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/MembersListItem.tsx @@ -32,8 +32,12 @@ interface Props { member: T.OrganizationMember; organization: T.Organization; organizationGroups: T.Group[]; - removeMember: (member: T.OrganizationMember) => void; - updateMemberGroups: (member: T.OrganizationMember, add: string[], remove: string[]) => void; + removeMember?: (member: T.OrganizationMember) => void; + updateMemberGroups: ( + member: T.OrganizationMember, + add: string[], + remove: string[] + ) => Promise<void>; } interface State { @@ -76,7 +80,7 @@ export default class MembersListItem extends React.PureComponent<Props, State> { }; render() { - const { member, organization } = this.props; + const { member, organization, removeMember } = this.props; const { actions = {} } = organization; return ( <tr> @@ -96,16 +100,20 @@ export default class MembersListItem extends React.PureComponent<Props, State> { </td> )} {actions.admin && ( - <React.Fragment> + <> <td className="nowrap text-middle text-right"> <ActionsDropdown> <ActionsDropdownItem onClick={this.handleManageGroupsClick}> {translate('organization.members.manage_groups')} </ActionsDropdownItem> - <ActionsDropdownDivider /> - <ActionsDropdownItem destructive={true} onClick={this.handleRemoveMemberClick}> - {translate('organization.members.remove')} - </ActionsDropdownItem> + {removeMember && ( + <> + <ActionsDropdownDivider /> + <ActionsDropdownItem destructive={true} onClick={this.handleRemoveMemberClick}> + {translate('organization.members.remove')} + </ActionsDropdownItem> + </> + )} </ActionsDropdown> </td> @@ -119,15 +127,16 @@ export default class MembersListItem extends React.PureComponent<Props, State> { /> )} - {this.state.removeMemberForm && ( - <RemoveMemberForm - member={this.props.member} - onClose={this.closeRemoveMemberForm} - organization={this.props.organization} - removeMember={this.props.removeMember} - /> - )} - </React.Fragment> + {removeMember && + this.state.removeMemberForm && ( + <RemoveMemberForm + member={this.props.member} + onClose={this.closeRemoveMemberForm} + organization={this.props.organization} + removeMember={removeMember} + /> + )} + </> )} </tr> ); diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/MembersPageHeader.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/MembersPageHeader.tsx index 9015c4ba8b2..5cf908cce7c 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/MembersPageHeader.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/MembersPageHeader.tsx @@ -18,121 +18,82 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { connect } from 'react-redux'; import { FormattedMessage } from 'react-intl'; import { Link } from 'react-router'; import AddMemberForm from './AddMemberForm'; import SyncMemberForm from './SyncMemberForm'; import DeferredSpinner from '../../components/common/DeferredSpinner'; import DocTooltip from '../../components/docs/DocTooltip'; -import NewInfoBox from '../../components/ui/NewInfoBox'; import { sanitizeAlmId } from '../../helpers/almIntegrations'; import { translate, translateWithParameters } from '../../helpers/l10n'; -import { getCurrentUserSetting, Store } from '../../store/rootReducer'; -import { setCurrentUserSetting } from '../../store/users'; +import { Alert } from '../../components/ui/Alert'; -interface Props { - dismissSyncNotifOrg: string[]; +export interface Props { handleAddMember: (member: T.OrganizationMember) => void; loading: boolean; members?: T.OrganizationMember[]; organization: T.Organization; refreshMembers: () => Promise<void>; - setCurrentUserSetting: (setting: T.CurrentUserSetting) => void; } -export class MembersPageHeader extends React.PureComponent<Props> { - handleDismissSyncNotif = () => { - const { dismissSyncNotifOrg, organization } = this.props; - this.props.setCurrentUserSetting({ - key: 'organizations.members.dismissSyncNotif', - value: [...dismissSyncNotifOrg, organization.key].join(',') - }); - }; +export default function MembersPageHeader(props: Props) { + const { members, organization, refreshMembers } = props; + const memberLogins = members ? members.map(member => member.login) : []; + const isAdmin = organization.actions && organization.actions.admin; + const almKey = organization.alm && sanitizeAlmId(organization.alm.key); + const hasMemberSync = organization.alm && organization.alm.membersSync; + const showSyncNotif = isAdmin && organization.alm && !hasMemberSync; - render() { - const { dismissSyncNotifOrg, members, organization, refreshMembers } = this.props; - const memberLogins = members ? members.map(member => member.login) : []; - const isAdmin = organization.actions && organization.actions.admin; - const almKey = organization.alm && sanitizeAlmId(organization.alm.key); - const hasMemberSync = organization.alm && organization.alm.membersSync; - const showSyncNotif = - isAdmin && - organization.alm && - !hasMemberSync && - !dismissSyncNotifOrg.some(orgKey => orgKey === organization.key); - - return ( - <header className="page-header"> - <h1 className="page-title">{translate('organization.members.page')}</h1> - <DeferredSpinner loading={this.props.loading} /> - {isAdmin && ( - <div className="page-actions text-right"> - {almKey && - !showSyncNotif && ( - <SyncMemberForm organization={organization} refreshMembers={refreshMembers} /> - )} - {!hasMemberSync && ( - <div className="display-inline-block spacer-left spacer-bottom"> - <AddMemberForm - addMember={this.props.handleAddMember} - memberLogins={memberLogins} - organization={organization} - /> - <DocTooltip - className="spacer-left" - doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/organizations/add-organization-member.md')} - /> - </div> + return ( + <header className="page-header"> + <h1 className="page-title">{translate('organization.members.page')}</h1> + <DeferredSpinner loading={props.loading} /> + {isAdmin && ( + <div className="page-actions text-right"> + {almKey && + !showSyncNotif && ( + <SyncMemberForm organization={organization} refreshMembers={refreshMembers} /> )} - {almKey && - showSyncNotif && ( - <NewInfoBox - description={translateWithParameters( - 'organization.members.auto_sync_members_from_org_x', - translate(almKey) - )} - onClose={this.handleDismissSyncNotif} - title={translateWithParameters( - 'organization.members.auto_sync_with_x', - translate(almKey) - )}> - <SyncMemberForm - dismissSyncNotif={this.handleDismissSyncNotif} - organization={organization} - refreshMembers={refreshMembers} - /> - </NewInfoBox> - )} - </div> - )} - <div className="page-description"> - <FormattedMessage - defaultMessage={translate('organization.members.page.description')} - id="organization.members.page.description" - values={{ - link: ( - <Link to="/documentation/organizations/manage-team/"> - {translate('organization.members.manage_a_team')} - </Link> - ) - }} - /> + {!hasMemberSync && ( + <div className="display-inline-block spacer-left spacer-bottom"> + <AddMemberForm + addMember={props.handleAddMember} + memberLogins={memberLogins} + organization={organization} + /> + <DocTooltip + className="spacer-left" + doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/organizations/add-organization-member.md')} + /> + </div> + )} </div> - </header> - ); - } + )} + <div className="page-description"> + <FormattedMessage + defaultMessage={translate('organization.members.page.description')} + id="organization.members.page.description" + values={{ + link: ( + <Link target="_blank" to="/documentation/organizations/manage-team/"> + {translate('organization.members.manage_a_team')} + </Link> + ) + }} + /> + {almKey && + showSyncNotif && ( + <Alert className="spacer-top" display="inline" variant="info"> + {translateWithParameters( + 'organization.members.auto_sync_members_from_org_x', + translate('organization', almKey) + )} + <span className="spacer-left"> + <SyncMemberForm organization={organization} refreshMembers={refreshMembers} /> + </span> + </Alert> + )} + </div> + </header> + ); } - -const mapStateToProps = (state: Store) => ({ - dismissSyncNotifOrg: ( - getCurrentUserSetting(state, 'organizations.members.dismissSyncNotif') || '' - ).split(',') -}); - -const mapDispatchToProps = { setCurrentUserSetting }; - -export default connect( - mapStateToProps, - mapDispatchToProps -)(MembersPageHeader); diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx index da2ea484d97..bd141ec20e3 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx @@ -182,22 +182,20 @@ export default class OrganizationMembers extends React.PureComponent<Props, Stat removeUserFromGroup({ name, login, organization: this.props.organization.key }) ) ]; - Promise.all(promises).then( - () => { - if (this.mounted) { - this.updateGroup(login, member => ({ - ...member, - groupCount: (member.groupCount || 0) + add.length - remove.length - })); - } - }, - () => {} - ); + return Promise.all(promises).then(() => { + if (this.mounted) { + this.updateGroup(login, member => ({ + ...member, + groupCount: (member.groupCount || 0) + add.length - remove.length + })); + } + }); }; render() { - const { organization } = this.props; + const { currentUser, organization } = this.props; const { groups, loading, members, paging } = this.state; + const hasMemberSync = organization.alm && organization.alm.membersSync; return ( <div className="page page-limited"> <Helmet title={translate('organization.members.page')} /> @@ -213,16 +211,17 @@ export default class OrganizationMembers extends React.PureComponent<Props, Stat paging !== undefined && ( <> <MembersListHeader - currentUser={this.props.currentUser} + currentUser={currentUser} handleSearch={this.handleSearchMembers} organization={organization} total={paging.total} /> <MembersList + currentUser={currentUser} members={members} organization={organization} organizationGroups={groups} - removeMember={this.handleRemoveMember} + removeMember={hasMemberSync ? undefined : this.handleRemoveMember} updateMemberGroups={this.updateMemberGroups} /> {paging.total !== 0 && ( diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/SyncMemberForm.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/SyncMemberForm.tsx index 9ebb9f57e21..9276aa1df56 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/SyncMemberForm.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/SyncMemberForm.tsx @@ -30,7 +30,6 @@ import { translate, translateWithParameters } from '../../helpers/l10n'; import { fetchOrganization } from '../../store/rootActions'; interface Props { - dismissSyncNotif?: () => void; fetchOrganization: (key: string) => void; organization: T.Organization; refreshMembers: () => Promise<void>; @@ -49,13 +48,9 @@ export class SyncMemberForm extends React.PureComponent<Props, State> { } handleConfirm = () => { - const { dismissSyncNotif, organization } = this.props; + const { organization } = this.props; const { membersSync } = this.state; - if (dismissSyncNotif) { - dismissSyncNotif(); - } - return setOrganizationMemberSync({ organization: organization.key, enabled: membersSync @@ -99,63 +94,55 @@ export class SyncMemberForm extends React.PureComponent<Props, State> { const { organization } = this.props; const almKey = organization.alm && sanitizeAlmId(organization.alm.key); return ( - <> - <div className="display-flex-stretch big-spacer-top"> - <RadioCard - onClick={this.handleManualClick} - selected={!membersSync} - title={translate('organization.members.management.manual')}> - <div className="spacer-left"> - <ul className="big-spacer-left note"> - <li className="spacer-bottom"> - {translate('organization.members.management.manual.add_members_manually')} - </li> - <li> - {translate('organization.members.management.manual.choose_members_permissions')} - </li> - </ul> - </div> - </RadioCard> - <RadioCard - onClick={this.handleAutoClick} - selected={membersSync} - title={translateWithParameters( - 'organization.members.management.automatic', - translate(almKey || '') - )}> - <div className="spacer-left"> - <ul className="big-spacer-left note"> - {almKey && ( - <> - <li className="spacer-bottom"> - {translateWithParameters( - 'organization.members.management.automatic.synchronized_from_x', - translate(almKey) - )} - </li> - <li className="spacer-bottom"> - {translate( - 'organization.members.management.automatic.members_changes_reflected', - almKey - )} - </li> - </> - )} - <li> - {translate( - 'organization.members.management.automatic.still_choose_members_permissions' - )} - </li> - </ul> - </div> - {(!organization.alm || !organization.alm.membersSync) && ( - <Alert className="big-spacer-top" variant="warning"> - {translate('organization.members.management.automatic.warning')} - </Alert> - )} - </RadioCard> - </div> - </> + <div className="display-flex-stretch big-spacer-top"> + <RadioCard + onClick={this.handleManualClick} + selected={!membersSync} + title={translate('organization.members.management.manual')}> + <div className="spacer-left"> + <ul className="big-spacer-left note"> + <li className="spacer-bottom"> + {translate('organization.members.management.manual.add_members_manually')} + </li> + <li>{translate('organization.members.management.choose_members_permissions')}</li> + </ul> + </div> + </RadioCard> + <RadioCard + onClick={this.handleAutoClick} + selected={membersSync} + title={translateWithParameters( + 'organization.members.management.automatic', + translate(almKey || '') + )}> + <div className="spacer-left"> + <ul className="big-spacer-left note"> + {almKey && ( + <> + <li className="spacer-bottom"> + {translateWithParameters( + 'organization.members.management.automatic.synchronized_from_x', + translate('organization', almKey) + )} + </li> + <li className="spacer-bottom"> + {translate( + 'organization.members.management.automatic.members_changes_reflected', + almKey + )} + </li> + </> + )} + <li>{translate('organization.members.management.choose_members_permissions')}</li> + </ul> + </div> + {(!organization.alm || !organization.alm.membersSync) && ( + <Alert className="big-spacer-top" variant="warning"> + {translate('organization.members.management.automatic.warning')} + </Alert> + )} + </RadioCard> + </div> ); }; @@ -167,14 +154,12 @@ export class SyncMemberForm extends React.PureComponent<Props, State> { cancelButtonText={translate('close')} confirmButtonText={translate('save')} confirmDisable={this.state.membersSync === orgMemberSync} - medium={true} modalBody={this.renderModalBody()} modalHeader={translate('organization.members.management.title')} modalHeaderDescription={this.renderModalDescription()} - onConfirm={this.handleConfirm}> - {({ onClick }) => ( - <Button onClick={onClick}>{translate('organization.members.config_synchro')}</Button> - )} + onConfirm={this.handleConfirm} + size={'medium'}> + {({ onClick }) => <Button onClick={onClick}>{translate('configure')}</Button>} </ConfirmButton> ); } diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/ManageMemberGroupsForm-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/ManageMemberGroupsForm-test.tsx index a412a1b679f..30b2f906974 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/ManageMemberGroupsForm-test.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/ManageMemberGroupsForm-test.tsx @@ -20,7 +20,6 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import ManageMemberGroupsForm from '../ManageMemberGroupsForm'; -import { mockEvent } from '../../../helpers/testMocks'; const member = { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }; const organization = { name: 'MyOrg', key: 'myorg' }; @@ -45,11 +44,17 @@ const organizationGroups = [ } ]; const userGroups = { - 11: { id: 11, name: 'pull-request-analysers', description: 'Technical accounts', selected: true } + '11': { + default: false, + id: 11, + name: 'pull-request-analysers', + description: 'Technical accounts', + selected: true + } }; -function getMountedForm(updateFunc = jest.fn()) { - const wrapper = shallow( +function getMountedForm(updateFunc = jest.fn().mockResolvedValue({})) { + const wrapper = shallow<ManageMemberGroupsForm>( <ManageMemberGroupsForm member={member} onClose={jest.fn()} @@ -75,27 +80,26 @@ it('should render', () => { /> ); expect(wrapper).toMatchSnapshot(); + expect(wrapper.dive()).toMatchSnapshot(); }); it('should correctly select the groups', () => { const form = getMountedForm(); - const instance = form.instance as ManageMemberGroupsForm; - expect(instance.isGroupSelected('11')).toBeTruthy(); - expect(instance.isGroupSelected('7')).toBeFalsy(); - instance.onCheck('11', false); - instance.onCheck('7', true); + expect(form.instance.isGroupSelected('11')).toBeTruthy(); + expect(form.instance.isGroupSelected('7')).toBeFalsy(); + form.instance.onCheck('11', false); + form.instance.onCheck('7', true); expect(form.wrapper.state('userGroups')).toMatchSnapshot(); - expect(instance.isGroupSelected('11')).toBeFalsy(); - expect(instance.isGroupSelected('7')).toBeTruthy(); + expect(form.instance.isGroupSelected('11')).toBeFalsy(); + expect(form.instance.isGroupSelected('7')).toBeTruthy(); }); it('should correctly handle the submit event and close the modal', () => { - const updateMemberGroups = jest.fn(); + const updateMemberGroups = jest.fn().mockResolvedValue({}); const form = getMountedForm(updateMemberGroups); - const instance = form.instance as ManageMemberGroupsForm; - instance.onCheck('11', false); - instance.onCheck('7', true); - instance.handleSubmit(mockEvent()); + form.instance.onCheck('11', false); + form.instance.onCheck('7', true); + form.instance.handleSubmit(); expect(updateMemberGroups.mock.calls).toMatchSnapshot(); expect(form.wrapper.state()).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersList-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersList-test.tsx index 2157332788c..f3746540b7a 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersList-test.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersList-test.tsx @@ -20,35 +20,31 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import MembersList from '../MembersList'; +import { mockOrganization, mockCurrentUser } from '../../../helpers/testMocks'; -const organization = { key: 'foo', name: 'Foo' }; const members = [ { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }, { login: 'john', name: 'John Doe', avatar: '7daf6c79d4802916d83f6266e24850af', groupCount: 1 } ]; it('should render a list of members of an organization', () => { - const wrapper = shallow( - <MembersList - members={members} - organization={organization} - organizationGroups={[]} - removeMember={jest.fn()} - updateMemberGroups={jest.fn()} - /> - ); - expect(wrapper).toMatchSnapshot(); + expect(shallowRender()).toMatchSnapshot(); }); it('should render "no results"', () => { - const wrapper = shallow( + expect(shallowRender({ members: [] })).toMatchSnapshot(); +}); + +function shallowRender(props: Partial<MembersList['props']> = {}) { + return shallow( <MembersList - members={[]} - organization={organization} + currentUser={mockCurrentUser({ login: 'admin' })} + members={members} + organization={mockOrganization()} organizationGroups={[]} removeMember={jest.fn()} updateMemberGroups={jest.fn()} + {...props} /> ); - expect(wrapper).toMatchSnapshot(); -}); +} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListItem-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListItem-test.tsx index 13a4ebfae5e..58d4daaf89c 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListItem-test.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListItem-test.tsx @@ -21,60 +21,30 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import MembersListItem from '../MembersListItem'; import { click } from '../../../helpers/testUtils'; - -const organization = { key: 'foo', name: 'Foo' }; -const admin = { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }; -const john = { login: 'john', name: 'John Doe', avatar: '7daf6c79d4802916d83f6266e24850af' }; +import { mockOrganizationWithAdminActions, mockOrganization } from '../../../helpers/testMocks'; it('should not render actions and groups for non admin', () => { - const wrapper = shallow( - <MembersListItem - member={admin} - organization={organization} - organizationGroups={[]} - removeMember={jest.fn()} - updateMemberGroups={jest.fn()} - /> - ); - expect(wrapper).toMatchSnapshot(); + expect(shallowRender({ organization: mockOrganization() })).toMatchSnapshot(); }); it('should render actions and groups for admin', () => { - const wrapper = shallow( - <MembersListItem - member={admin} - organization={{ ...organization, actions: { admin: true } }} - organizationGroups={[]} - removeMember={jest.fn()} - updateMemberGroups={jest.fn()} - /> - ); - expect(wrapper).toMatchSnapshot(); + expect(shallowRender()).toMatchSnapshot(); }); -it('should groups at 0 if the groupCount field is not defined (just added user)', () => { - const wrapper = shallow( - <MembersListItem - member={john} - organization={{ ...organization, actions: { admin: true } }} - organizationGroups={[]} - removeMember={jest.fn()} - updateMemberGroups={jest.fn()} - /> - ); - expect(wrapper).toMatchSnapshot(); +it('should show groups at 0 if the groupCount field is not defined (just added user)', () => { + expect( + shallowRender({ + member: { login: 'john', name: 'John Doe', avatar: '7daf6c79d4802916d83f6266e24850af' } + }) + ).toMatchSnapshot(); +}); + +it('should not display the remove member action', () => { + expect(shallowRender({ removeMember: undefined }).find('ActionsDropdown')).toMatchSnapshot(); }); it('should open groups form', () => { - const wrapper = shallow( - <MembersListItem - member={admin} - organization={{ ...organization, actions: { admin: true } }} - organizationGroups={[]} - removeMember={jest.fn()} - updateMemberGroups={jest.fn()} - /> - ); + const wrapper = shallowRender(); click(wrapper.find('ActionsDropdownItem').first()); expect(wrapper.find('ManageMemberGroupsForm').exists()).toBe(true); @@ -85,15 +55,7 @@ it('should open groups form', () => { }); it('should open remove member form', () => { - const wrapper = shallow( - <MembersListItem - member={admin} - organization={{ ...organization, actions: { admin: true } }} - organizationGroups={[]} - removeMember={jest.fn()} - updateMemberGroups={jest.fn()} - /> - ); + const wrapper = shallowRender(); click(wrapper.find('ActionsDropdownItem').last()); expect(wrapper.find('RemoveMemberForm').exists()).toBe(true); @@ -102,3 +64,16 @@ it('should open remove member form', () => { wrapper.update(); expect(wrapper.find('RemoveMemberForm').exists()).toBe(false); }); + +function shallowRender(props: Partial<MembersListItem['props']> = {}) { + return shallow( + <MembersListItem + member={{ login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }} + organization={mockOrganizationWithAdminActions()} + organizationGroups={[]} + removeMember={jest.fn()} + updateMemberGroups={jest.fn()} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersPageHeader-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersPageHeader-test.tsx index bb2a10ad805..bc81f0149f4 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersPageHeader-test.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersPageHeader-test.tsx @@ -19,7 +19,7 @@ */ import * as React from 'react'; import { shallow } from 'enzyme'; -import { MembersPageHeader } from '../MembersPageHeader'; +import MembersPageHeader, { Props } from '../MembersPageHeader'; import { mockOrganization, mockOrganizationWithAlm, @@ -39,10 +39,6 @@ it('should render for admin', () => { it('should render for bound organization without sync', () => { const organization = mockOrganizationWithAlm(mockOrganizationWithAdminActions()); expect(shallowRender({ organization })).toMatchSnapshot(); - - const wrapper = shallowRender({ organization, dismissSyncNotifOrg: [organization.key] }); - expect(wrapper.find('Connect(SyncMemberForm)').exists()).toBe(true); - expect(wrapper.find('NewInfoBox').exists()).toBe(false); }); it('should render for bound organization with sync', () => { @@ -52,19 +48,17 @@ it('should render for bound organization with sync', () => { const wrapper = shallowRender({ organization }); expect(wrapper.find('Connect(SyncMemberForm)').exists()).toBe(true); expect(wrapper.find('AddMemberForm').exists()).toBe(false); - expect(wrapper.find('NewInfoBox').exists()).toBe(false); + expect(wrapper.find('Alert').exists()).toBe(false); }); -function shallowRender(props: Partial<MembersPageHeader['props']> = {}) { +function shallowRender(props: Partial<Props> = {}) { return shallow( <MembersPageHeader - dismissSyncNotifOrg={[]} handleAddMember={jest.fn()} loading={false} members={[]} organization={mockOrganization()} refreshMembers={jest.fn()} - setCurrentUserSetting={jest.fn()} {...props} /> ); diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/OrganizationMembers-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/OrganizationMembers-test.tsx index eac606f79c0..1b119209666 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/OrganizationMembers-test.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/OrganizationMembers-test.tsx @@ -25,7 +25,8 @@ import { searchUsersGroups, addUserToGroup, removeUserFromGroup } from '../../.. import { mockOrganization, mockCurrentUser, - mockOrganizationWithAdminActions + mockOrganizationWithAdminActions, + mockOrganizationWithAlm } from '../../../helpers/testMocks'; import { waitAndUpdate } from '../../../helpers/testUtils'; @@ -112,7 +113,7 @@ it('should refresh members', async () => { it('should add new member', async () => { const wrapper = shallowRender(); await waitAndUpdate(wrapper); - wrapper.find('Connect(MembersPageHeader)').prop<Function>('handleAddMember')({ login: 'bar' }); + wrapper.find('MembersPageHeader').prop<Function>('handleAddMember')({ login: 'bar' }); await waitAndUpdate(wrapper); expect( wrapper @@ -160,6 +161,14 @@ it('should update groups', async () => { expect(removeUserFromGroup).toBeCalledWith({ login: 'john', name: 'birds', organization: 'foo' }); }); +it('should not allow to remove members when in sync mode', async () => { + const wrapper = shallowRender({ + organization: mockOrganizationWithAlm(mockOrganizationWithAdminActions(), { membersSync: true }) + }); + await waitAndUpdate(wrapper); + expect(wrapper.find('MembersList').prop('removeMember')).toBeUndefined(); +}); + function shallowRender(props: Partial<OrganizationMembers['props']> = {}) { return shallow<OrganizationMembers>( <OrganizationMembers diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/SyncMemberForm-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/SyncMemberForm-test.tsx index 78dd230299a..1218009c845 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/SyncMemberForm-test.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/SyncMemberForm-test.tsx @@ -30,15 +30,13 @@ jest.mock('../../../api/organizations', () => ({ })); beforeEach(() => { - (setOrganizationMemberSync as jest.Mock).mockClear(); - (syncMembers as jest.Mock).mockClear(); + jest.clearAllMocks(); }); it('should allow to switch to automatic mode with github', async () => { - const dismissSyncNotif = jest.fn(); const fetchOrganization = jest.fn(); const refreshMembers = jest.fn().mockResolvedValue({}); - const wrapper = shallowRender({ dismissSyncNotif, fetchOrganization, refreshMembers }); + const wrapper = shallowRender({ fetchOrganization, refreshMembers }); expect(wrapper).toMatchSnapshot(); wrapper.setState({ membersSync: true }); @@ -48,8 +46,7 @@ it('should allow to switch to automatic mode with github', async () => { await waitAndUpdate(wrapper); expect(fetchOrganization).toHaveBeenCalledWith('foo'); expect(syncMembers).toHaveBeenCalledWith('foo'); - expect(refreshMembers).toBeCalledTimes(1); - expect(dismissSyncNotif).toBeCalledTimes(1); + expect(refreshMembers).toBeCalled(); }); it('should allow to switch to automatic mode with bitbucket', async () => { diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/AddMemberForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/AddMemberForm-test.tsx.snap index 895c48986d7..5eafb110cf2 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/AddMemberForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/AddMemberForm-test.tsx.snap @@ -38,7 +38,7 @@ exports[`should render and open the modal 2`] = ` className="modal-body" > <div - className="modal-large-field" + className="modal-field" > <label> users.search_description diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap index 660a194e06c..8a87fc3aa2b 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap @@ -24,6 +24,7 @@ Object { "loading": false, "userGroups": Object { "11": Object { + "default": false, "description": "Technical accounts", "id": 11, "name": "pull-request-analysers", @@ -40,6 +41,7 @@ Object { exports[`should correctly select the groups 1`] = ` Object { "11": Object { + "default": false, "description": "Technical accounts", "id": 11, "name": "pull-request-analysers", @@ -53,44 +55,62 @@ Object { `; exports[`should render 1`] = ` +<SimpleModal + header="organization.members.manage_groups" + onClose={[MockFunction]} + onSubmit={[Function]} +> + <Component /> +</SimpleModal> +`; + +exports[`should render 2`] = ` <Modal contentLabel="organization.members.manage_groups" onRequestClose={[MockFunction]} > - <header - className="modal-head" - > - <h2> - organization.members.manage_groups - </h2> - </header> <form onSubmit={[Function]} > + <header + className="modal-head" + > + <h2> + organization.members.manage_groups + </h2> + </header> <div className="modal-body modal-container" > - <strong> - organization.members.members_groups.Admin Istrator - </strong> - - <i - className="spinner" + <p> + <strong> + organization.members.members_groups.Admin Istrator + </strong> + </p> + <DeferredSpinner + className="spacer-top" + timeout={100} /> </div> <footer className="modal-foot" > - <div> - <SubmitButton> - save - </SubmitButton> - <ResetButtonLink - onClick={[MockFunction]} - > - cancel - </ResetButtonLink> - </div> + <DeferredSpinner + className="spacer-right" + loading={false} + timeout={100} + /> + <SubmitButton + disabled={true} + > + save + </SubmitButton> + <ResetButtonLink + disabled={false} + onClick={[Function]} + > + cancel + </ResetButtonLink> </footer> </form> </Modal> diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersList-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersList-test.tsx.snap index 9635b1e2685..a99cabd208d 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersList-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersList-test.tsx.snap @@ -33,7 +33,6 @@ exports[`should render a list of members of an organization 1`] = ` } } organizationGroups={Array []} - removeMember={[MockFunction]} updateMemberGroups={[MockFunction]} /> <MembersListItem diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListHeader-test.tsx.snap index 1d8ecfb92df..e9852b6c3c3 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListHeader-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListHeader-test.tsx.snap @@ -5,7 +5,7 @@ exports[`should not render link in help tooltip 1`] = ` className="spacer-left" overlay={ <div - className="abs-width-300 markdown cut-margins" + className="abs-width-300 markdown cut-margins" > <p> organization.members.auto_sync_total_help.github @@ -20,7 +20,7 @@ exports[`should render a help tooltip 1`] = ` className="spacer-left" overlay={ <div - className="abs-width-300 markdown cut-margins" + className="abs-width-300 markdown cut-margins" > <p> organization.members.auto_sync_total_help.github @@ -47,7 +47,7 @@ exports[`should render a help tooltip 2`] = ` className="spacer-left" overlay={ <div - className="abs-width-300 markdown cut-margins" + className="abs-width-300 markdown cut-margins" > <p> organization.members.auto_sync_total_help.bitbucket diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListItem-test.tsx.snap index b97c6a82959..414c9f4f30e 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListItem-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListItem-test.tsx.snap @@ -1,13 +1,23 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should groups at 0 if the groupCount field is not defined (just added user) 1`] = ` +exports[`should not display the remove member action 1`] = ` +<ActionsDropdown> + <ActionsDropdownItem + onClick={[Function]} + > + organization.members.manage_groups + </ActionsDropdownItem> +</ActionsDropdown> +`; + +exports[`should not render actions and groups for non admin 1`] = ` <tr> <td className="thin nowrap" > <Connect(Avatar) - hash="7daf6c79d4802916d83f6266e24850af" - name="John Doe" + hash="" + name="Admin Istrator" size={36} /> </td> @@ -15,41 +25,18 @@ exports[`should groups at 0 if the groupCount field is not defined (just added u className="nowrap text-middle" > <strong> - John Doe + Admin Istrator </strong> <span className="note little-spacer-left" > - john + admin </span> </td> - <td - className="text-right text-middle" - > - organization.members.x_groups.0 - </td> - <td - className="nowrap text-middle text-right" - > - <ActionsDropdown> - <ActionsDropdownItem - onClick={[Function]} - > - organization.members.manage_groups - </ActionsDropdownItem> - <ActionsDropdownDivider /> - <ActionsDropdownItem - destructive={true} - onClick={[Function]} - > - organization.members.remove - </ActionsDropdownItem> - </ActionsDropdown> - </td> </tr> `; -exports[`should not render actions and groups for non admin 1`] = ` +exports[`should render actions and groups for admin 1`] = ` <tr> <td className="thin nowrap" @@ -72,17 +59,40 @@ exports[`should not render actions and groups for non admin 1`] = ` admin </span> </td> + <td + className="text-right text-middle" + > + organization.members.x_groups.3 + </td> + <td + className="nowrap text-middle text-right" + > + <ActionsDropdown> + <ActionsDropdownItem + onClick={[Function]} + > + organization.members.manage_groups + </ActionsDropdownItem> + <ActionsDropdownDivider /> + <ActionsDropdownItem + destructive={true} + onClick={[Function]} + > + organization.members.remove + </ActionsDropdownItem> + </ActionsDropdown> + </td> </tr> `; -exports[`should render actions and groups for admin 1`] = ` +exports[`should show groups at 0 if the groupCount field is not defined (just added user) 1`] = ` <tr> <td className="thin nowrap" > <Connect(Avatar) - hash="" - name="Admin Istrator" + hash="7daf6c79d4802916d83f6266e24850af" + name="John Doe" size={36} /> </td> @@ -90,18 +100,18 @@ exports[`should render actions and groups for admin 1`] = ` className="nowrap text-middle" > <strong> - Admin Istrator + John Doe </strong> <span className="note little-spacer-left" > - admin + john </span> </td> <td className="text-right text-middle" > - organization.members.x_groups.3 + organization.members.x_groups.0 </td> <td className="nowrap text-middle text-right" diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersPageHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersPageHeader-test.tsx.snap index 0225976a5a6..c21bb177f1c 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersPageHeader-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersPageHeader-test.tsx.snap @@ -24,6 +24,7 @@ exports[`should render correctly 1`] = ` "link": <Link onlyActiveOnIndex={false} style={Object {}} + target="_blank" to="/documentation/organizations/manage-team/" > organization.members.manage_a_team @@ -84,6 +85,7 @@ exports[`should render for admin 1`] = ` "link": <Link onlyActiveOnIndex={false} style={Object {}} + target="_blank" to="/documentation/organizations/manage-team/" > organization.members.manage_a_team @@ -137,30 +139,6 @@ exports[`should render for bound organization without sync 1`] = ` doc={Promise {}} /> </div> - <NewInfoBox - description="organization.members.auto_sync_members_from_org_x.github" - onClose={[Function]} - title="organization.members.auto_sync_with_x.github" - > - <Connect(SyncMemberForm) - dismissSyncNotif={[Function]} - organization={ - Object { - "actions": Object { - "admin": true, - }, - "alm": Object { - "key": "github", - "membersSync": false, - "url": "https://github.com/foo", - }, - "key": "foo", - "name": "Foo", - } - } - refreshMembers={[MockFunction]} - /> - </NewInfoBox> </div> <div className="page-description" @@ -173,6 +151,7 @@ exports[`should render for bound organization without sync 1`] = ` "link": <Link onlyActiveOnIndex={false} style={Object {}} + target="_blank" to="/documentation/organizations/manage-team/" > organization.members.manage_a_team @@ -180,6 +159,34 @@ exports[`should render for bound organization without sync 1`] = ` } } /> + <Alert + className="spacer-top" + display="inline" + variant="info" + > + organization.members.auto_sync_members_from_org_x.organization.github + <span + className="spacer-left" + > + <Connect(SyncMemberForm) + organization={ + Object { + "actions": Object { + "admin": true, + }, + "alm": Object { + "key": "github", + "membersSync": false, + "url": "https://github.com/foo", + }, + "key": "foo", + "name": "Foo", + } + } + refreshMembers={[MockFunction]} + /> + </span> + </Alert> </div> </header> `; diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/OrganizationMembers-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/OrganizationMembers-test.tsx.snap index 2d451e1086c..9f3f3c11a3c 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/OrganizationMembers-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/OrganizationMembers-test.tsx.snap @@ -12,7 +12,7 @@ exports[`should fetch members and render for non-admin 1`] = ` <Suggestions suggestions="organization_members" /> - <Connect(MembersPageHeader) + <MembersPageHeader handleAddMember={[Function]} loading={true} organization={ @@ -38,7 +38,7 @@ exports[`should fetch members and render for non-admin 2`] = ` <Suggestions suggestions="organization_members" /> - <Connect(MembersPageHeader) + <MembersPageHeader handleAddMember={[Function]} loading={false} members={ @@ -85,6 +85,15 @@ exports[`should fetch members and render for non-admin 2`] = ` total={3} /> <MembersList + currentUser={ + Object { + "groups": Array [], + "isLoggedIn": true, + "login": "luke", + "name": "Skywalker", + "scmAccounts": Array [], + } + } members={ Array [ Object { diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/SyncMemberForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/SyncMemberForm-test.tsx.snap index c7c59107ab3..1be6de36a4c 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/SyncMemberForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/SyncMemberForm-test.tsx.snap @@ -5,71 +5,68 @@ exports[`should allow to switch to automatic mode with bitbucket 1`] = ` cancelButtonText="close" confirmButtonText="save" confirmDisable={true} - medium={true} modalBody={ - <React.Fragment> - <div - className="display-flex-stretch big-spacer-top" + <div + className="display-flex-stretch big-spacer-top" + > + <RadioCard + onClick={[Function]} + selected={true} + title="organization.members.management.manual" > - <RadioCard - onClick={[Function]} - selected={true} - title="organization.members.management.manual" + <div + className="spacer-left" > - <div - className="spacer-left" + <ul + className="big-spacer-left note" > - <ul - className="big-spacer-left note" + <li + className="spacer-bottom" > + organization.members.management.manual.add_members_manually + </li> + <li> + organization.members.management.choose_members_permissions + </li> + </ul> + </div> + </RadioCard> + <RadioCard + onClick={[Function]} + selected={false} + title="organization.members.management.automatic.bitbucket" + > + <div + className="spacer-left" + > + <ul + className="big-spacer-left note" + > + <React.Fragment> <li className="spacer-bottom" > - organization.members.management.manual.add_members_manually + organization.members.management.automatic.synchronized_from_x.organization.bitbucket </li> - <li> - organization.members.management.manual.choose_members_permissions + <li + className="spacer-bottom" + > + organization.members.management.automatic.members_changes_reflected.bitbucket </li> - </ul> - </div> - </RadioCard> - <RadioCard - onClick={[Function]} - selected={false} - title="organization.members.management.automatic.bitbucket" + </React.Fragment> + <li> + organization.members.management.choose_members_permissions + </li> + </ul> + </div> + <Alert + className="big-spacer-top" + variant="warning" > - <div - className="spacer-left" - > - <ul - className="big-spacer-left note" - > - <React.Fragment> - <li - className="spacer-bottom" - > - organization.members.management.automatic.synchronized_from_x.bitbucket - </li> - <li - className="spacer-bottom" - > - organization.members.management.automatic.members_changes_reflected.bitbucket - </li> - </React.Fragment> - <li> - organization.members.management.automatic.still_choose_members_permissions - </li> - </ul> - </div> - <Alert - className="big-spacer-top" - variant="warning" - > - organization.members.management.automatic.warning - </Alert> - </RadioCard> - </div> - </React.Fragment> + organization.members.management.automatic.warning + </Alert> + </RadioCard> + </div> } modalHeader="organization.members.management.title" modalHeaderDescription={ @@ -93,6 +90,7 @@ exports[`should allow to switch to automatic mode with bitbucket 1`] = ` </p> } onConfirm={[Function]} + size="medium" > <Component /> </ConfirmButton> @@ -103,71 +101,68 @@ exports[`should allow to switch to automatic mode with github 1`] = ` cancelButtonText="close" confirmButtonText="save" confirmDisable={true} - medium={true} modalBody={ - <React.Fragment> - <div - className="display-flex-stretch big-spacer-top" + <div + className="display-flex-stretch big-spacer-top" + > + <RadioCard + onClick={[Function]} + selected={true} + title="organization.members.management.manual" > - <RadioCard - onClick={[Function]} - selected={true} - title="organization.members.management.manual" + <div + className="spacer-left" > - <div - className="spacer-left" + <ul + className="big-spacer-left note" > - <ul - className="big-spacer-left note" + <li + className="spacer-bottom" > + organization.members.management.manual.add_members_manually + </li> + <li> + organization.members.management.choose_members_permissions + </li> + </ul> + </div> + </RadioCard> + <RadioCard + onClick={[Function]} + selected={false} + title="organization.members.management.automatic.github" + > + <div + className="spacer-left" + > + <ul + className="big-spacer-left note" + > + <React.Fragment> <li className="spacer-bottom" > - organization.members.management.manual.add_members_manually + organization.members.management.automatic.synchronized_from_x.organization.github </li> - <li> - organization.members.management.manual.choose_members_permissions + <li + className="spacer-bottom" + > + organization.members.management.automatic.members_changes_reflected.github </li> - </ul> - </div> - </RadioCard> - <RadioCard - onClick={[Function]} - selected={false} - title="organization.members.management.automatic.github" + </React.Fragment> + <li> + organization.members.management.choose_members_permissions + </li> + </ul> + </div> + <Alert + className="big-spacer-top" + variant="warning" > - <div - className="spacer-left" - > - <ul - className="big-spacer-left note" - > - <React.Fragment> - <li - className="spacer-bottom" - > - organization.members.management.automatic.synchronized_from_x.github - </li> - <li - className="spacer-bottom" - > - organization.members.management.automatic.members_changes_reflected.github - </li> - </React.Fragment> - <li> - organization.members.management.automatic.still_choose_members_permissions - </li> - </ul> - </div> - <Alert - className="big-spacer-top" - variant="warning" - > - organization.members.management.automatic.warning - </Alert> - </RadioCard> - </div> - </React.Fragment> + organization.members.management.automatic.warning + </Alert> + </RadioCard> + </div> } modalHeader="organization.members.management.title" modalHeaderDescription={ @@ -191,6 +186,7 @@ exports[`should allow to switch to automatic mode with github 1`] = ` </p> } onConfirm={[Function]} + size="medium" > <Component /> </ConfirmButton> @@ -201,65 +197,62 @@ exports[`should allow to switch to manual mode 1`] = ` cancelButtonText="close" confirmButtonText="save" confirmDisable={true} - medium={true} modalBody={ - <React.Fragment> - <div - className="display-flex-stretch big-spacer-top" + <div + className="display-flex-stretch big-spacer-top" + > + <RadioCard + onClick={[Function]} + selected={false} + title="organization.members.management.manual" > - <RadioCard - onClick={[Function]} - selected={false} - title="organization.members.management.manual" + <div + className="spacer-left" > - <div - className="spacer-left" + <ul + className="big-spacer-left note" > - <ul - className="big-spacer-left note" + <li + className="spacer-bottom" > + organization.members.management.manual.add_members_manually + </li> + <li> + organization.members.management.choose_members_permissions + </li> + </ul> + </div> + </RadioCard> + <RadioCard + onClick={[Function]} + selected={true} + title="organization.members.management.automatic.github" + > + <div + className="spacer-left" + > + <ul + className="big-spacer-left note" + > + <React.Fragment> <li className="spacer-bottom" > - organization.members.management.manual.add_members_manually - </li> - <li> - organization.members.management.manual.choose_members_permissions + organization.members.management.automatic.synchronized_from_x.organization.github </li> - </ul> - </div> - </RadioCard> - <RadioCard - onClick={[Function]} - selected={true} - title="organization.members.management.automatic.github" - > - <div - className="spacer-left" - > - <ul - className="big-spacer-left note" - > - <React.Fragment> - <li - className="spacer-bottom" - > - organization.members.management.automatic.synchronized_from_x.github - </li> - <li - className="spacer-bottom" - > - organization.members.management.automatic.members_changes_reflected.github - </li> - </React.Fragment> - <li> - organization.members.management.automatic.still_choose_members_permissions + <li + className="spacer-bottom" + > + organization.members.management.automatic.members_changes_reflected.github </li> - </ul> - </div> - </RadioCard> - </div> - </React.Fragment> + </React.Fragment> + <li> + organization.members.management.choose_members_permissions + </li> + </ul> + </div> + </RadioCard> + </div> } modalHeader="organization.members.management.title" modalHeaderDescription={ @@ -283,6 +276,7 @@ exports[`should allow to switch to manual mode 1`] = ` </p> } onConfirm={[Function]} + size="medium" > <Component /> </ConfirmButton> diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.tsx index 452950f680d..4d151adf2ab 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.tsx +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.tsx @@ -111,12 +111,13 @@ export class OrganizationEdit extends React.PureComponent<Props, State> { <div className="boxed-group boxed-group-inner"> <form onSubmit={this.handleSubmit}> - <div className="modal-field"> + <div className="form-field"> <label htmlFor="organization-name"> {translate('organization.name')} <em className="mandatory">*</em> </label> <input + className="input-super-large" disabled={this.state.loading} id="organization-name" maxLength={255} @@ -126,13 +127,14 @@ export class OrganizationEdit extends React.PureComponent<Props, State> { type="text" value={this.state.name} /> - <div className="modal-field-description"> + <div className="form-field-description"> {translate('organization.name.description')} </div> </div> - <div className="modal-field"> + <div className="form-field"> <label htmlFor="organization-avatar">{translate('organization.avatar')}</label> <input + className="input-super-large" disabled={this.state.loading} id="organization-avatar" maxLength={256} @@ -142,11 +144,11 @@ export class OrganizationEdit extends React.PureComponent<Props, State> { type="text" value={this.state.avatar} /> - <div className="modal-field-description"> + <div className="form-field-description"> {translate('organization.avatar.description')} </div> {(this.state.avatarImage || this.state.name) && ( - <div className="spacer-top spacer-bottom"> + <div className="spacer-top"> <div className="little-spacer-bottom"> {translate('organization.avatar.preview')} {':'} @@ -160,9 +162,10 @@ export class OrganizationEdit extends React.PureComponent<Props, State> { </div> )} </div> - <div className="modal-field"> + <div className="form-field"> <label htmlFor="organization-description">{translate('description')}</label> <textarea + className="input-super-large" disabled={this.state.loading} id="organization-description" maxLength={256} @@ -171,13 +174,14 @@ export class OrganizationEdit extends React.PureComponent<Props, State> { rows={3} value={this.state.description} /> - <div className="modal-field-description"> + <div className="form-field-description"> {translate('organization.description.description')} </div> </div> - <div className="modal-field"> + <div className="form-field"> <label htmlFor="organization-url">{translate('organization.url')}</label> <input + className="input-super-large" disabled={this.state.loading} id="organization-url" maxLength={256} @@ -186,14 +190,12 @@ export class OrganizationEdit extends React.PureComponent<Props, State> { type="text" value={this.state.url} /> - <div className="modal-field-description"> + <div className="form-field-description"> {translate('organization.url.description')} </div> </div> - <div className="modal-field"> - <SubmitButton disabled={this.state.loading}>{translate('save')}</SubmitButton> - {this.state.loading && <i className="spinner spacer-left" />} - </div> + <SubmitButton disabled={this.state.loading}>{translate('save')}</SubmitButton> + {this.state.loading && <i className="spinner spacer-left" />} </form> </div> </div> diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.css b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.css index 80a428e111c..19e438405fe 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.css +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.css @@ -19,5 +19,5 @@ */ .organization-empty { margin: 100px auto 0; - width: 800px; + width: 472px; } diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.tsx index 4310bb215a9..078ba67c231 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.tsx +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.tsx @@ -50,19 +50,15 @@ export class OrganizationEmpty extends React.PureComponent<Props> { return ( <div className="organization-empty"> <h3 className="text-center">{translate('onboarding.create_organization.ready')}</h3> - <div className="onboarding-choices"> - <Button className="onboarding-choice" onClick={this.handleNewProjectClick}> + <div className="display-flex-space-around huge-spacer-top"> + <Button className="button-huge" onClick={this.handleNewProjectClick}> <OnboardingProjectIcon className="big-spacer-bottom" /> - <h6 className="onboarding-choice-name"> - {translate('provisioning.analyze_new_project')} - </h6> + <p className="medium spacer-top">{translate('provisioning.analyze_new_project')}</p> </Button> {!memberSyncActivated && ( - <Button className="onboarding-choice" onClick={this.handleAddMembersClick}> + <Button className="button-huge" onClick={this.handleAddMembersClick}> <OnboardingAddMembersIcon /> - <h6 className="onboarding-choice-name"> - {translate('organization.members.add.multiple')} - </h6> + <p className="medium spacer-top">{translate('organization.members.add.multiple')}</p> </Button> )} </div> diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.tsx.snap index 22b55f2ca3e..e948f1810c5 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.tsx.snap @@ -25,7 +25,7 @@ exports[`smoke test 1`] = ` onSubmit={[Function]} > <div - className="modal-field" + className="form-field" > <label htmlFor="organization-name" @@ -38,6 +38,7 @@ exports[`smoke test 1`] = ` </em> </label> <input + className="input-super-large" disabled={false} id="organization-name" maxLength={255} @@ -48,13 +49,13 @@ exports[`smoke test 1`] = ` value="Foo" /> <div - className="modal-field-description" + className="form-field-description" > organization.name.description </div> </div> <div - className="modal-field" + className="form-field" > <label htmlFor="organization-avatar" @@ -62,6 +63,7 @@ exports[`smoke test 1`] = ` organization.avatar </label> <input + className="input-super-large" disabled={false} id="organization-avatar" maxLength={256} @@ -72,12 +74,12 @@ exports[`smoke test 1`] = ` value="" /> <div - className="modal-field-description" + className="form-field-description" > organization.avatar.description </div> <div - className="spacer-top spacer-bottom" + className="spacer-top" > <div className="little-spacer-bottom" @@ -96,7 +98,7 @@ exports[`smoke test 1`] = ` </div> </div> <div - className="modal-field" + className="form-field" > <label htmlFor="organization-description" @@ -104,6 +106,7 @@ exports[`smoke test 1`] = ` description </label> <textarea + className="input-super-large" disabled={false} id="organization-description" maxLength={256} @@ -113,13 +116,13 @@ exports[`smoke test 1`] = ` value="" /> <div - className="modal-field-description" + className="form-field-description" > organization.description.description </div> </div> <div - className="modal-field" + className="form-field" > <label htmlFor="organization-url" @@ -127,6 +130,7 @@ exports[`smoke test 1`] = ` organization.url </label> <input + className="input-super-large" disabled={false} id="organization-url" maxLength={256} @@ -136,20 +140,16 @@ exports[`smoke test 1`] = ` value="" /> <div - className="modal-field-description" + className="form-field-description" > organization.url.description </div> </div> - <div - className="modal-field" + <SubmitButton + disabled={false} > - <SubmitButton - disabled={false} - > - save - </SubmitButton> - </div> + save + </SubmitButton> </form> </div> </div> @@ -180,7 +180,7 @@ exports[`smoke test 2`] = ` onSubmit={[Function]} > <div - className="modal-field" + className="form-field" > <label htmlFor="organization-name" @@ -193,6 +193,7 @@ exports[`smoke test 2`] = ` </em> </label> <input + className="input-super-large" disabled={false} id="organization-name" maxLength={255} @@ -203,13 +204,13 @@ exports[`smoke test 2`] = ` value="New Foo" /> <div - className="modal-field-description" + className="form-field-description" > organization.name.description </div> </div> <div - className="modal-field" + className="form-field" > <label htmlFor="organization-avatar" @@ -217,6 +218,7 @@ exports[`smoke test 2`] = ` organization.avatar </label> <input + className="input-super-large" disabled={false} id="organization-avatar" maxLength={256} @@ -227,12 +229,12 @@ exports[`smoke test 2`] = ` value="foo-avatar" /> <div - className="modal-field-description" + className="form-field-description" > organization.avatar.description </div> <div - className="spacer-top spacer-bottom" + className="spacer-top" > <div className="little-spacer-bottom" @@ -251,7 +253,7 @@ exports[`smoke test 2`] = ` </div> </div> <div - className="modal-field" + className="form-field" > <label htmlFor="organization-description" @@ -259,6 +261,7 @@ exports[`smoke test 2`] = ` description </label> <textarea + className="input-super-large" disabled={false} id="organization-description" maxLength={256} @@ -268,13 +271,13 @@ exports[`smoke test 2`] = ` value="foo-description" /> <div - className="modal-field-description" + className="form-field-description" > organization.description.description </div> </div> <div - className="modal-field" + className="form-field" > <label htmlFor="organization-url" @@ -282,6 +285,7 @@ exports[`smoke test 2`] = ` organization.url </label> <input + className="input-super-large" disabled={false} id="organization-url" maxLength={256} @@ -291,20 +295,16 @@ exports[`smoke test 2`] = ` value="foo-url" /> <div - className="modal-field-description" + className="form-field-description" > organization.url.description </div> </div> - <div - className="modal-field" + <SubmitButton + disabled={false} > - <SubmitButton - disabled={false} - > - save - </SubmitButton> - </div> + save + </SubmitButton> </form> </div> </div> @@ -335,7 +335,7 @@ exports[`smoke test 3`] = ` onSubmit={[Function]} > <div - className="modal-field" + className="form-field" > <label htmlFor="organization-name" @@ -348,6 +348,7 @@ exports[`smoke test 3`] = ` </em> </label> <input + className="input-super-large" disabled={true} id="organization-name" maxLength={255} @@ -358,13 +359,13 @@ exports[`smoke test 3`] = ` value="New Foo" /> <div - className="modal-field-description" + className="form-field-description" > organization.name.description </div> </div> <div - className="modal-field" + className="form-field" > <label htmlFor="organization-avatar" @@ -372,6 +373,7 @@ exports[`smoke test 3`] = ` organization.avatar </label> <input + className="input-super-large" disabled={true} id="organization-avatar" maxLength={256} @@ -382,12 +384,12 @@ exports[`smoke test 3`] = ` value="foo-avatar" /> <div - className="modal-field-description" + className="form-field-description" > organization.avatar.description </div> <div - className="spacer-top spacer-bottom" + className="spacer-top" > <div className="little-spacer-bottom" @@ -406,7 +408,7 @@ exports[`smoke test 3`] = ` </div> </div> <div - className="modal-field" + className="form-field" > <label htmlFor="organization-description" @@ -414,6 +416,7 @@ exports[`smoke test 3`] = ` description </label> <textarea + className="input-super-large" disabled={true} id="organization-description" maxLength={256} @@ -423,13 +426,13 @@ exports[`smoke test 3`] = ` value="foo-description" /> <div - className="modal-field-description" + className="form-field-description" > organization.description.description </div> </div> <div - className="modal-field" + className="form-field" > <label htmlFor="organization-url" @@ -437,6 +440,7 @@ exports[`smoke test 3`] = ` organization.url </label> <input + className="input-super-large" disabled={true} id="organization-url" maxLength={256} @@ -446,23 +450,19 @@ exports[`smoke test 3`] = ` value="foo-url" /> <div - className="modal-field-description" + className="form-field-description" > organization.url.description </div> </div> - <div - className="modal-field" + <SubmitButton + disabled={true} > - <SubmitButton - disabled={true} - > - save - </SubmitButton> - <i - className="spinner spacer-left" - /> - </div> + save + </SubmitButton> + <i + className="spinner spacer-left" + /> </form> </div> </div> diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEmpty-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEmpty-test.tsx.snap index d58fec02a6c..82a33f255a1 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEmpty-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEmpty-test.tsx.snap @@ -10,20 +10,20 @@ exports[`should hide add members button when member sync activated 1`] = ` onboarding.create_organization.ready </h3> <div - className="onboarding-choices" + className="display-flex-space-around huge-spacer-top" > <Button - className="onboarding-choice" + className="button-huge" onClick={[Function]} > <OnboardingProjectIcon className="big-spacer-bottom" /> - <h6 - className="onboarding-choice-name" + <p + className="medium spacer-top" > provisioning.analyze_new_project - </h6> + </p> </Button> </div> </div> @@ -39,31 +39,31 @@ exports[`should render 1`] = ` onboarding.create_organization.ready </h3> <div - className="onboarding-choices" + className="display-flex-space-around huge-spacer-top" > <Button - className="onboarding-choice" + className="button-huge" onClick={[Function]} > <OnboardingProjectIcon className="big-spacer-bottom" /> - <h6 - className="onboarding-choice-name" + <p + className="medium spacer-top" > provisioning.analyze_new_project - </h6> + </p> </Button> <Button - className="onboarding-choice" + className="button-huge" onClick={[Function]} > <OnboardingAddMembersIcon /> - <h6 - className="onboarding-choice-name" + <p + className="medium spacer-top" > organization.members.add.multiple - </h6> + </p> </Button> </div> </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 9b040c23812..f3eb1a147d9 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 @@ -20,17 +20,17 @@ 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'; +import { translate } from '../../../helpers/l10n'; interface Props { confirmButtonText: string; header: string; - permissionTemplate?: { description?: string; name: string; projectKeyPattern?: string }; onClose: () => void; onSubmit: ( data: { description: string; name: string; projectKeyPattern: string } ) => Promise<void>; + permissionTemplate?: { description?: string; name: string; projectKeyPattern?: string }; } interface State { @@ -79,7 +79,8 @@ export default class Form extends React.PureComponent<Props, State> { <SimpleModal header={this.props.header} onClose={this.props.onClose} - onSubmit={this.handleSubmit}> + onSubmit={this.handleSubmit} + size="small"> {({ onCloseClick, onFormSubmit, submitting }) => ( <form id="permission-template-form" onSubmit={onFormSubmit}> <header className="modal-head"> diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/Form-test.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/Form-test.tsx new file mode 100644 index 00000000000..1d8dfd32c52 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/Form-test.tsx @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import Form from '../Form'; + +it('render correctly', () => { + expect( + shallow( + <Form confirmButtonText="confirm" header="title" onClose={jest.fn()} onSubmit={jest.fn()} /> + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/Form-test.tsx.snap b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/Form-test.tsx.snap new file mode 100644 index 00000000000..81c1df3dd78 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/Form-test.tsx.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`render correctly 1`] = ` +<SimpleModal + header="title" + onClose={[MockFunction]} + onSubmit={[Function]} + size="small" +> + <Component /> +</SimpleModal> +`; 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 bd5538c91b0..d27ac5ad9f8 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 @@ -98,7 +98,11 @@ export default class ApplyTemplate extends React.PureComponent<Props, State> { ); return ( - <SimpleModal header={header} onClose={this.props.onClose} onSubmit={this.handleSubmit}> + <SimpleModal + header={header} + onClose={this.props.onClose} + onSubmit={this.handleSubmit} + size="small"> {({ onCloseClick, onFormSubmit, submitting }) => ( <form id="project-permissions-apply-template-form" onSubmit={onFormSubmit}> <header className="modal-head"> diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/ApplyTemplate-test.tsx b/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/ApplyTemplate-test.tsx new file mode 100644 index 00000000000..221b9e9e504 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/ApplyTemplate-test.tsx @@ -0,0 +1,53 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import ApplyTemplate from '../ApplyTemplate'; +import { waitAndUpdate } from '../../../../../helpers/testUtils'; + +jest.mock('../../../../../api/permissions', () => ({ + getPermissionTemplates: jest.fn().mockResolvedValue({ + permissionTemplates: [ + { + id: 'tmp1', + name: 'SonarSource projects', + createdAt: '2015-11-27T15:20:32+0100', + permissions: [ + { key: 'admin', usersCount: 0, groupsCount: 3 }, + { key: 'codeviewer', usersCount: 0, groupsCount: 6 } + ] + } + ], + defaultTemplates: [{ templateId: 'tmp1', qualifier: 'TRK' }], + permissions: [ + { key: 'admin', name: 'Administer', description: 'Administer access' }, + { key: 'codeviewer', name: 'See Source Code', description: 'View code' } + ] + }) +})); + +it('render correctly', async () => { + const wrapper = shallow( + <ApplyTemplate onClose={jest.fn()} organization="foo" project={{ key: 'foo', name: 'Foo' }} /> + ); + expect(wrapper).toMatchSnapshot(); + await waitAndUpdate(wrapper); + expect(wrapper.dive()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/__snapshots__/ApplyTemplate-test.tsx.snap b/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/__snapshots__/ApplyTemplate-test.tsx.snap new file mode 100644 index 00000000000..84bd72b0667 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/__snapshots__/ApplyTemplate-test.tsx.snap @@ -0,0 +1,83 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`render correctly 1`] = ` +<SimpleModal + header="projects_role.apply_template_to_xxx.Foo" + onClose={[MockFunction]} + onSubmit={[Function]} + size="small" +> + <Component /> +</SimpleModal> +`; + +exports[`render correctly 2`] = ` +<Modal + contentLabel="projects_role.apply_template_to_xxx.Foo" + onRequestClose={[MockFunction]} + size="small" +> + <form + id="project-permissions-apply-template-form" + onSubmit={[Function]} + > + <header + className="modal-head" + > + <h2> + projects_role.apply_template_to_xxx.Foo + </h2> + </header> + <div + className="modal-body" + > + <div + className="modal-field" + > + <label + htmlFor="project-permissions-template" + > + template + <em + className="mandatory" + > + * + </em> + </label> + <Select + clearable={false} + id="project-permissions-template" + onChange={[Function]} + options={ + Array [ + Object { + "label": "SonarSource projects", + "value": "tmp1", + }, + ] + } + /> + </div> + </div> + <footer + className="modal-foot" + > + <DeferredSpinner + className="spacer-right" + loading={false} + timeout={100} + /> + <SubmitButton + disabled={true} + > + apply + </SubmitButton> + <ResetButtonLink + onClick={[Function]} + > + cancel + </ResetButtonLink> + </footer> + </form> +</Modal> +`; diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.tsx index 98bf37efb85..1f5bb6c51bf 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.tsx +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.tsx @@ -51,7 +51,8 @@ export default class AddEventForm extends React.PureComponent<Props, State> { confirmDisable={!this.state.name} header={translate(this.props.addEventButtonText)} onClose={this.props.onClose} - onConfirm={this.handleSubmit}> + onConfirm={this.handleSubmit} + size="small"> <div className="modal-field"> <label>{translate('name')}</label> <input diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeEventForm.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeEventForm.tsx index c566bbfc450..22680c097ad 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeEventForm.tsx +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeEventForm.tsx @@ -23,8 +23,8 @@ import ConfirmModal from '../../../../components/controls/ConfirmModal'; interface Props { changeEvent: (event: string, name: string) => Promise<void>; - header: string; event: T.AnalysisEvent; + header: string; onClose: () => void; } @@ -54,7 +54,8 @@ export default class ChangeEventForm extends React.PureComponent<Props, State> { confirmDisable={!name || name === this.props.event.name} header={this.props.header} onClose={this.props.onClose} - onConfirm={this.handleSubmit}> + onConfirm={this.handleSubmit} + size="small"> <div className="modal-field"> <label>{translate('name')}</label> <input autoFocus={true} onChange={this.changeInput} type="text" value={name} /> diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/AddEventForm-test.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/AddEventForm-test.tsx new file mode 100644 index 00000000000..68791270850 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/AddEventForm-test.tsx @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import AddEventForm from '../AddEventForm'; + +it('should render correctly', () => { + expect( + shallow( + <AddEventForm + addEvent={jest.fn()} + addEventButtonText="add" + analysis={{ + key: '1', + date: new Date('2019-01-14T15:44:51.000Z'), + events: [{ key: '2', category: 'VERSION', name: '1.0' }], + codePeriodVersion: '1.0', + manualNewCodePeriodBaseline: false + }} + onClose={jest.fn()} + /> + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/ChangeEventForm-test.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/ChangeEventForm-test.tsx new file mode 100644 index 00000000000..4c739f499f7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/ChangeEventForm-test.tsx @@ -0,0 +1,35 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import ChangeEventForm from '../ChangeEventForm'; + +it('should render correctly', () => { + expect( + shallow( + <ChangeEventForm + changeEvent={jest.fn()} + event={{ category: 'VERSION', key: '1', name: '1.0' }} + header="change" + onClose={jest.fn()} + /> + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/__snapshots__/AddEventForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/__snapshots__/AddEventForm-test.tsx.snap new file mode 100644 index 00000000000..2331d990381 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/__snapshots__/AddEventForm-test.tsx.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<ConfirmModal + confirmButtonText="save" + confirmDisable={true} + header="add" + onClose={[MockFunction]} + onConfirm={[Function]} + size="small" +> + <div + className="modal-field" + > + <label> + name + </label> + <input + autoFocus={true} + onChange={[Function]} + type="text" + value="" + /> + </div> +</ConfirmModal> +`; diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/__snapshots__/ChangeEventForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/__snapshots__/ChangeEventForm-test.tsx.snap new file mode 100644 index 00000000000..70debbd5a51 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/__snapshots__/ChangeEventForm-test.tsx.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<ConfirmModal + confirmButtonText="change_verb" + confirmDisable={true} + header="change" + onClose={[MockFunction]} + onConfirm={[Function]} + size="small" +> + <div + className="modal-field" + > + <label> + name + </label> + <input + autoFocus={true} + onChange={[Function]} + type="text" + value="1.0" + /> + </div> +</ConfirmModal> +`; 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 3ce5526a0fb..0a5ae1e36f0 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 @@ -79,7 +79,7 @@ export default class RenameBranchModal extends React.PureComponent<Props, State> this.state.loading || !this.state.name || this.state.name === branch.name; return ( - <Modal contentLabel={header} onRequestClose={this.props.onClose}> + <Modal contentLabel={header} onRequestClose={this.props.onClose} size="small"> <header className="modal-head"> <h2>{header}</h2> </header> 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 6a486e563eb..6cfbcee0d44 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 @@ -98,7 +98,7 @@ export default class SettingForm extends React.PureComponent<Props, State> { className="big-spacer-bottom markdown" dangerouslySetInnerHTML={{ __html: translate(`property.${setting.key}.description`) }} /> - <div className="big-spacer-bottom"> + <div className="modal-field"> <input autoFocus={true} className="input-super-large" @@ -108,11 +108,11 @@ export default class SettingForm extends React.PureComponent<Props, State> { value={this.state.value} /> {setting.inherited && ( - <div className="note spacer-top">{translate('settings._default')}</div> + <div className="modal-field-description">{translate('settings._default')}</div> )} {!setting.inherited && setting.parentValue && ( - <div className="note spacer-top"> + <div className="modal-field-description"> {translateWithParameters('settings.default_x', setting.parentValue)} </div> )} 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 77c4b64b7f1..fc11d119916 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 @@ -4,6 +4,7 @@ exports[`renders 1`] = ` <Modal contentLabel="branches.rename" onRequestClose={[MockFunction]} + size="small" > <header className="modal-head" @@ -66,6 +67,7 @@ exports[`renders 2`] = ` <Modal contentLabel="branches.rename" onRequestClose={[MockFunction]} + size="small" > <header className="modal-head" @@ -128,6 +130,7 @@ exports[`renders 3`] = ` <Modal contentLabel="branches.rename" onRequestClose={[MockFunction]} + size="small" > <header className="modal-head" 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 1c038c720ff..45cb96b4022 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 @@ -16,7 +16,7 @@ exports[`changes value 1`] = ` } /> <div - className="big-spacer-bottom" + className="modal-field" > <input autoFocus={true} @@ -27,7 +27,7 @@ exports[`changes value 1`] = ` value="release-.*" /> <div - className="note spacer-top" + className="modal-field-description" > settings._default </div> @@ -66,7 +66,7 @@ exports[`resets value 1`] = ` } /> <div - className="big-spacer-bottom" + className="modal-field" > <input autoFocus={true} @@ -77,7 +77,7 @@ exports[`resets value 1`] = ` value="release-.*" /> <div - className="note spacer-top" + className="modal-field-description" > settings.default_x.branch-.* </div> diff --git a/server/sonar-web/src/main/js/apps/projectLinks/CreationModal.tsx b/server/sonar-web/src/main/js/apps/projectLinks/CreationModal.tsx index 4b6285a29d2..5ad2acbd937 100644 --- a/server/sonar-web/src/main/js/apps/projectLinks/CreationModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectLinks/CreationModal.tsx @@ -52,7 +52,11 @@ export default class CreationModal extends React.PureComponent<Props, State> { const header = translate('project_links.create_new_project_link'); return ( - <SimpleModal header={header} onClose={this.props.onClose} onSubmit={this.handleSubmit}> + <SimpleModal + header={header} + onClose={this.props.onClose} + onSubmit={this.handleSubmit} + size="small"> {({ onCloseClick, onFormSubmit, submitting }) => ( <form onSubmit={onFormSubmit}> <header className="modal-head"> diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/CreationModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/CreationModal-test.tsx.snap index b56b44cef77..b46aff716c6 100644 --- a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/CreationModal-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/CreationModal-test.tsx.snap @@ -4,6 +4,7 @@ exports[`should create link 1`] = ` <Modal contentLabel="project_links.create_new_project_link" onRequestClose={[MockFunction]} + size="small" > <form onSubmit={[Function]} 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 63d8ae46b7f..86e8b4e1dce 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx @@ -152,7 +152,7 @@ export default class BulkApplyTemplateModal extends React.PureComponent<Props, S const header = translate('permission_templates.bulk_apply_permission_template'); return ( - <Modal contentLabel={header} onRequestClose={this.props.onClose}> + <Modal contentLabel={header} onRequestClose={this.props.onClose} size="small"> <header className="modal-head"> <h2>{header}</h2> </header> 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 dd78ed4eb3c..a916e81d16e 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx @@ -199,8 +199,9 @@ export default class CreateProjectForm extends React.PureComponent<Props, State> {organization.actions && organization.actions.admin && !organization.canUpdateProjectsVisibilityToPrivate && ( - <div className="spacer-top display-flex-space-around"> + <div className="spacer-top"> <UpgradeOrganizationBox + className="width-100" insideModal={true} onOrganizationUpgrade={this.props.onOrganizationUpgrade} organization={organization} 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 3582abeb9cf..9b7ddf1b306 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 @@ -4,6 +4,7 @@ exports[`bulk applies template to all results 1`] = ` <Modal contentLabel="permission_templates.bulk_apply_permission_template" onRequestClose={[MockFunction]} + size="small" > <header className="modal-head" @@ -36,6 +37,7 @@ exports[`bulk applies template to all results 2`] = ` <Modal contentLabel="permission_templates.bulk_apply_permission_template" onRequestClose={[MockFunction]} + size="small" > <header className="modal-head" @@ -106,6 +108,7 @@ exports[`bulk applies template to all results 3`] = ` <Modal contentLabel="permission_templates.bulk_apply_permission_template" onRequestClose={[MockFunction]} + size="small" > <header className="modal-head" @@ -179,6 +182,7 @@ exports[`bulk applies template to all results 4`] = ` <Modal contentLabel="permission_templates.bulk_apply_permission_template" onRequestClose={[MockFunction]} + size="small" > <header className="modal-head" @@ -213,6 +217,7 @@ exports[`bulk applies template to selected results 1`] = ` <Modal contentLabel="permission_templates.bulk_apply_permission_template" onRequestClose={[MockFunction]} + size="small" > <header className="modal-head" @@ -245,6 +250,7 @@ exports[`bulk applies template to selected results 2`] = ` <Modal contentLabel="permission_templates.bulk_apply_permission_template" onRequestClose={[MockFunction]} + size="small" > <header className="modal-head" @@ -315,6 +321,7 @@ exports[`bulk applies template to selected results 3`] = ` <Modal contentLabel="permission_templates.bulk_apply_permission_template" onRequestClose={[MockFunction]} + size="small" > <header className="modal-head" @@ -388,6 +395,7 @@ exports[`bulk applies template to selected results 4`] = ` <Modal contentLabel="permission_templates.bulk_apply_permission_template" onRequestClose={[MockFunction]} + size="small" > <header className="modal-head" 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 859052104d3..9879d2fd163 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 @@ -79,9 +79,10 @@ exports[`creates project 1`] = ` /> </div> <div - className="spacer-top display-flex-space-around" + className="spacer-top" > <UpgradeOrganizationBox + className="width-100" insideModal={true} onOrganizationUpgrade={[MockFunction]} organization={ @@ -196,9 +197,10 @@ exports[`creates project 2`] = ` /> </div> <div - className="spacer-top display-flex-space-around" + className="spacer-top" > <UpgradeOrganizationBox + className="width-100" insideModal={true} onOrganizationUpgrade={[MockFunction]} organization={ @@ -313,9 +315,10 @@ exports[`creates project 3`] = ` /> </div> <div - className="spacer-top display-flex-space-around" + className="spacer-top" > <UpgradeOrganizationBox + className="width-100" insideModal={true} onOrganizationUpgrade={[MockFunction]} organization={ diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx index 7284f830995..92f9219f92c 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx @@ -107,7 +107,8 @@ export default class ConditionModal extends React.PureComponent<Props, State> { confirmDisable={metric === undefined} header={header} onClose={onClose} - onConfirm={this.handleFormSubmit}> + onConfirm={this.handleFormSubmit} + size="small"> {this.state.errorMessage && <Alert variant="error">{this.state.errorMessage}</Alert>} <div className="modal-field"> <label htmlFor="condition-metric">{translate('quality_gates.conditions.metric')}</label> @@ -118,7 +119,7 @@ export default class ConditionModal extends React.PureComponent<Props, State> { </div> {metric && ( <> - <div className="modal-field"> + <div className="modal-field display-inline-block"> <label htmlFor="condition-operator"> {translate('quality_gates.conditions.operator')} </label> @@ -128,7 +129,7 @@ export default class ConditionModal extends React.PureComponent<Props, State> { op={op} /> </div> - <div className="modal-field"> + <div className="modal-field display-inline-block spacer-left"> <label htmlFor="condition-threshold"> {translate('quality_gates.conditions.error')} </label> diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionOperator.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionOperator.tsx index de16855c471..b2cb17a398e 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionOperator.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionOperator.tsx @@ -23,9 +23,9 @@ import { translate } from '../../../helpers/l10n'; import { getPossibleOperators } from '../utils'; interface Props { - op?: string; metric: T.Metric; onOperatorChange: (op: string) => void; + op?: string; } export default class ConditionOperator extends React.PureComponent<Props> { @@ -62,7 +62,11 @@ export default class ConditionOperator extends React.PureComponent<Props> { /> ); } else { - return <span className="note">{this.getLabel(operators, this.props.metric)}</span>; + return ( + <span className="display-inline-block note abs-width-150"> + {this.getLabel(operators, this.props.metric)} + </span> + ); } } } 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 e6fc44670df..a631c273c05 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 @@ -71,7 +71,8 @@ class CopyQualityGateForm extends React.PureComponent<Props, State> { confirmDisable={confirmDisable} header={translate('quality_gates.copy')} onClose={this.props.onClose} - onConfirm={this.handleCopy}> + onConfirm={this.handleCopy} + size="small"> <div className="modal-field"> <label htmlFor="quality-gate-form-name"> {translate('name')} 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 a2ffb8d04b3..c310bb32ab7 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 @@ -67,7 +67,8 @@ class CreateQualityGateForm extends React.PureComponent<Props, State> { confirmDisable={!name} header={translate('quality_gates.create')} onClose={this.props.onClose} - onConfirm={this.handleCreate}> + onConfirm={this.handleCreate} + size="small"> <div className="modal-field"> <label htmlFor="quality-gate-form-name"> {translate('name')} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx index 17c3da58fcb..86bba914825 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx @@ -41,7 +41,8 @@ interface Option { export default class MetricSelect extends React.PureComponent<Props, State> { state = { value: -1 }; - handleChange = ({ value }: Option) => { + handleChange = (option: Option | null) => { + const value = option ? option.value : -1; this.setState({ value }); this.props.onMetricChange(this.props.metrics[value]); }; @@ -74,7 +75,7 @@ export default class MetricSelect extends React.PureComponent<Props, State> { return ( <Select - className="text-middle input-large" + className="text-middle" id="condition-metric" onChange={this.handleChange} options={optionsWithDomains} 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 b468b58d614..30bc667f7b7 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 @@ -67,7 +67,8 @@ export default class RenameQualityGateForm extends React.PureComponent<Props, St confirmDisable={confirmDisable} header={translate('quality_gates.rename')} onClose={this.props.onClose} - onConfirm={this.handleRename}> + onConfirm={this.handleRename} + size="small"> <div className="modal-field"> <label htmlFor="quality-gate-form-name"> {translate('name')} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionModal-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionModal-test.tsx new file mode 100644 index 00000000000..b1769f7a610 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionModal-test.tsx @@ -0,0 +1,43 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import ConditionModal from '../ConditionModal'; +import { mockQualityGate } from '../../../../helpers/testMocks'; + +it('should render correctly', () => { + const wrapper = shallowRender(); + expect(wrapper).toMatchSnapshot(); + + wrapper.instance().handleMetricChange({ id: '1', key: 'foo', name: 'Foo', type: 'PERCENT' }); + expect(wrapper).toMatchSnapshot(); +}); + +function shallowRender(props: Partial<ConditionModal['props']> = {}) { + return shallow<ConditionModal>( + <ConditionModal + header="a" + onAddCondition={jest.fn()} + onClose={jest.fn()} + qualityGate={mockQualityGate()} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionOperator-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionOperator-test.tsx new file mode 100644 index 00000000000..ef6c9fd2776 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionOperator-test.tsx @@ -0,0 +1,37 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import ConditionOperator from '../ConditionOperator'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot(); +}); + +function shallowRender(props: Partial<ConditionOperator['props']> = {}) { + return shallow( + <ConditionOperator + metric={{ id: '1', key: 'foo', name: 'Foo', type: 'PERCENT' }} + onOperatorChange={jest.fn()} + op="LT" + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/NewInfoBox-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/CopyQualityGateForm-test.tsx index d7fc93c6a11..85310abd443 100644 --- a/server/sonar-web/src/main/js/components/ui/__tests__/NewInfoBox-test.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/CopyQualityGateForm-test.tsx @@ -19,26 +19,13 @@ */ import * as React from 'react'; import { shallow } from 'enzyme'; -import NewInfoBox from '../NewInfoBox'; -import { click } from '../../../helpers/testUtils'; +import CopyQualityGateForm from '../CopyQualityGateForm'; +import { mockQualityGate } from '../../../../helpers/testMocks'; it('should render correctly', () => { expect( shallow( - <NewInfoBox description="My description" onClose={jest.fn()} title="My title"> - <div /> - </NewInfoBox> + <CopyQualityGateForm onClose={jest.fn()} onCopy={jest.fn()} qualityGate={mockQualityGate()} /> ) ).toMatchSnapshot(); }); - -it('should allow to opt out', () => { - const onClose = jest.fn(); - const wrapper = shallow( - <NewInfoBox description="" onClose={onClose} title=""> - <div /> - </NewInfoBox> - ); - click(wrapper.find('ButtonIcon')); - expect(onClose).toHaveBeenCalled(); -}); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/CreateQualityGateForm-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/CreateQualityGateForm-test.tsx new file mode 100644 index 00000000000..dde2fafa955 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/CreateQualityGateForm-test.tsx @@ -0,0 +1,28 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import CreateQualityGateForm from '../CreateQualityGateForm'; + +it('should render correctly', () => { + expect( + shallow(<CreateQualityGateForm onClose={jest.fn()} onCreate={jest.fn()} />) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/MetricSelect-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/MetricSelect-test.tsx new file mode 100644 index 00000000000..ad2d9fea3f3 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/MetricSelect-test.tsx @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import MetricSelect from '../MetricSelect'; + +it('should render correctly', () => { + expect( + shallow( + <MetricSelect + metrics={[ + { + id: '1', + key: '1', + name: 'metric 1', + type: 'test' + } + ]} + onMetricChange={jest.fn()} + /> + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/RenameQualityGateForm-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/RenameQualityGateForm-test.tsx new file mode 100644 index 00000000000..1ed1d7d18eb --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/RenameQualityGateForm-test.tsx @@ -0,0 +1,35 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import RenameQualityGateForm from '../RenameQualityGateForm'; +import { mockQualityGate } from '../../../../helpers/testMocks'; + +it('should render correctly', () => { + expect( + shallow( + <RenameQualityGateForm + onClose={jest.fn()} + onRename={jest.fn()} + qualityGate={mockQualityGate()} + /> + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionModal-test.tsx.snap new file mode 100644 index 00000000000..ab32f89c02b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionModal-test.tsx.snap @@ -0,0 +1,85 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<ConfirmModal + confirmButtonText="a" + confirmDisable={true} + header="a" + onClose={[MockFunction]} + onConfirm={[Function]} + size="small" +> + <div + className="modal-field" + > + <label + htmlFor="condition-metric" + > + quality_gates.conditions.metric + </label> + </div> +</ConfirmModal> +`; + +exports[`should render correctly 2`] = ` +<ConfirmModal + confirmButtonText="a" + confirmDisable={false} + header="a" + onClose={[MockFunction]} + onConfirm={[Function]} + size="small" +> + <div + className="modal-field" + > + <label + htmlFor="condition-metric" + > + quality_gates.conditions.metric + </label> + </div> + <div + className="modal-field display-inline-block" + > + <label + htmlFor="condition-operator" + > + quality_gates.conditions.operator + </label> + <ConditionOperator + metric={ + Object { + "id": "1", + "key": "foo", + "name": "Foo", + "type": "PERCENT", + } + } + onOperatorChange={[Function]} + /> + </div> + <div + className="modal-field display-inline-block spacer-left" + > + <label + htmlFor="condition-threshold" + > + quality_gates.conditions.error + </label> + <ThresholdInput + metric={ + Object { + "id": "1", + "key": "foo", + "name": "Foo", + "type": "PERCENT", + } + } + name="error" + onChange={[Function]} + value="" + /> + </div> +</ConfirmModal> +`; diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionOperator-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionOperator-test.tsx.snap new file mode 100644 index 00000000000..a229502920f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionOperator-test.tsx.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<Select + autoFocus={true} + className="input-medium" + clearable={false} + id="condition-operator" + name="operator" + onChange={[Function]} + options={ + Array [ + Object { + "label": "quality_gates.operator.LT", + "value": "LT", + }, + Object { + "label": "quality_gates.operator.GT", + "value": "GT", + }, + ] + } + searchable={false} + value="LT" +/> +`; diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/CopyQualityGateForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/CopyQualityGateForm-test.tsx.snap new file mode 100644 index 00000000000..dae3160544a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/CopyQualityGateForm-test.tsx.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<CopyQualityGateForm + onClose={[MockFunction]} + onCopy={[MockFunction]} + qualityGate={ + Object { + "id": 1, + "name": "qualitygate", + } + } +/> +`; diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/CreateQualityGateForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/CreateQualityGateForm-test.tsx.snap new file mode 100644 index 00000000000..d7702455fbe --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/CreateQualityGateForm-test.tsx.snap @@ -0,0 +1,8 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<CreateQualityGateForm + onClose={[MockFunction]} + onCreate={[MockFunction]} +/> +`; diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/MetricSelect-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/MetricSelect-test.tsx.snap new file mode 100644 index 00000000000..b03dceb54a2 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/MetricSelect-test.tsx.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<Select + className="text-middle" + id="condition-metric" + onChange={[Function]} + options={ + Array [ + Object { + "domain": undefined, + "label": "metric 1", + "value": 0, + }, + ] + } + placeholder="search.search_for_metrics" + value={-1} +/> +`; diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/RenameQualityGateForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/RenameQualityGateForm-test.tsx.snap new file mode 100644 index 00000000000..3a7393891bf --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/RenameQualityGateForm-test.tsx.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<ConfirmModal + confirmButtonText="rename" + confirmDisable={true} + header="quality_gates.rename" + onClose={[MockFunction]} + onConfirm={[Function]} + size="small" +> + <div + className="modal-field" + > + <label + htmlFor="quality-gate-form-name" + > + name + <em + className="mandatory" + > + * + </em> + </label> + <input + autoFocus={true} + id="quality-gate-form-name" + maxLength={100} + onChange={[Function]} + required={true} + size={50} + type="text" + value="qualitygate" + /> + </div> +</ConfirmModal> +`; 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 64ac78e752c..bcd4405a9c2 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 @@ -80,7 +80,7 @@ export default class CopyProfileForm extends React.PureComponent<Props, State> { this.state.loading || !this.state.name || this.state.name === profile.name; return ( - <Modal contentLabel={header} onRequestClose={this.props.onClose}> + <Modal contentLabel={header} onRequestClose={this.props.onClose} size="small"> <form id="copy-profile-form" onSubmit={this.handleFormSubmit}> <div className="modal-head"> <h2>{header}</h2> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ExtendProfileForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/ExtendProfileForm.tsx index 6b8fd8db20d..a686ceb5e18 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/components/ExtendProfileForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/ExtendProfileForm.tsx @@ -96,7 +96,7 @@ export default class ExtendProfileForm extends React.PureComponent<Props, State> ); return ( - <Modal contentLabel={header} onRequestClose={this.props.onClose}> + <Modal contentLabel={header} onRequestClose={this.props.onClose} size="small"> <form> <div className="modal-head"> <h2>{header}</h2> 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 1f3ebc9114b..714dda6cce8 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 @@ -80,7 +80,7 @@ export default class RenameProfileForm extends React.PureComponent<Props, State> this.state.loading || !this.state.name || this.state.name === profile.name; return ( - <Modal contentLabel={header} onRequestClose={this.props.onClose}> + <Modal contentLabel={header} onRequestClose={this.props.onClose} size="small"> <form id="rename-profile-form" onSubmit={this.handleFormSubmit}> <div className="modal-head"> <h2>{header}</h2> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/ExtendProfileForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/ExtendProfileForm-test.tsx.snap index f0dbf25d18c..d055f1c8968 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/ExtendProfileForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/ExtendProfileForm-test.tsx.snap @@ -4,6 +4,7 @@ exports[`should render correctly 1`] = ` <Modal contentLabel="quality_profiles.extend_x_title.name.JavaScript" onRequestClose={[MockFunction]} + size="small" > <form> <div 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 e44df26c5b5..1795c8e7045 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 @@ -95,7 +95,8 @@ export default class ChangeParentForm extends React.PureComponent<Props, State> return ( <Modal contentLabel={translate('quality_profiles.change_parent')} - onRequestClose={this.props.onClose}> + onRequestClose={this.props.onClose} + size="small"> <form id="change-profile-parent-form" onSubmit={this.handleFormSubmit}> <div className="modal-head"> <h2>{translate('quality_profiles.change_parent')}</h2> 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 35539ffc368..2cb0872c786 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 @@ -119,7 +119,7 @@ export default class ProfilePermissionsForm extends React.PureComponent<Props, S </header> <form onSubmit={this.handleFormSubmit}> <div className="modal-body"> - <div className="modal-large-field"> + <div className="modal-field"> <label>{translate('quality_profiles.search_description')}</label> <ProfilePermissionsFormSelect onChange={this.handleValueChange} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissionsFormSelect.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissionsFormSelect.tsx index acf1d98288d..9cf5a9df4b2 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissionsFormSelect.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissionsFormSelect.tsx @@ -122,15 +122,15 @@ function getStringValue(option: Option) { function optionRenderer(option: OptionWithValue) { return isUser(option) ? ( - <div> + <> <Avatar hash={option.avatar} name={option.name} size={16} /> <strong className="spacer-left">{option.name}</strong> <span className="note little-spacer-left">{option.login}</span> - </div> + </> ) : ( - <div> + <> <GroupIcon size={16} /> <strong className="spacer-left">{option.name}</strong> - </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 4caa41a48c1..8d1e901d79a 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 @@ -19,7 +19,7 @@ exports[`adds group 1`] = ` className="modal-body" > <div - className="modal-large-field" + className="modal-field" > <label> quality_profiles.search_description @@ -67,7 +67,7 @@ exports[`adds group 2`] = ` className="modal-body" > <div - className="modal-large-field" + className="modal-field" > <label> quality_profiles.search_description @@ -120,7 +120,7 @@ exports[`adds group 3`] = ` className="modal-body" > <div - className="modal-large-field" + className="modal-field" > <label> quality_profiles.search_description @@ -176,7 +176,7 @@ exports[`adds user 1`] = ` className="modal-body" > <div - className="modal-large-field" + className="modal-field" > <label> quality_profiles.search_description @@ -224,7 +224,7 @@ exports[`adds user 2`] = ` className="modal-body" > <div - className="modal-large-field" + className="modal-field" > <label> quality_profiles.search_description @@ -277,7 +277,7 @@ exports[`adds user 3`] = ` className="modal-body" > <div - className="modal-large-field" + className="modal-field" > <label> quality_profiles.search_description 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 876f85a88a5..7b19310ca2f 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 @@ -134,7 +134,7 @@ export default class CreateProfileForm extends React.PureComponent<Props, State> } return ( - <Modal contentLabel={header} onRequestClose={this.props.onClose}> + <Modal contentLabel={header} onRequestClose={this.props.onClose} size="small"> <form id="create-profile-form" onSubmit={this.handleFormSubmit}> <div className="modal-head"> <h2>{header}</h2> @@ -163,7 +163,7 @@ export default class CreateProfileForm extends React.PureComponent<Props, State> value={this.state.name} /> </div> - <div className="modal-field spacer-bottom"> + <div className="modal-field"> <label htmlFor="create-profile-language"> {translate('language')} <em className="mandatory">*</em> 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 51dcf1783f7..d8462260ce2 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 @@ -79,13 +79,34 @@ export default class RestoreProfileForm extends React.PureComponent<Props, State ); }; + renderAlert(profile: { name: string }, ruleFailures = 0, ruleSuccesses: number): React.ReactNode { + return ruleFailures ? ( + <Alert variant="warning"> + {translateWithParameters( + 'quality_profiles.restore_profile.warning', + profile.name, + ruleSuccesses, + ruleFailures + )} + </Alert> + ) : ( + <Alert variant="success"> + {translateWithParameters( + 'quality_profiles.restore_profile.success', + profile.name, + ruleSuccesses + )} + </Alert> + ); + } + render() { const header = translate('quality_profiles.restore_profile'); const { loading, profile, ruleFailures, ruleSuccesses } = this.state; return ( - <Modal contentLabel={header} onRequestClose={this.props.onClose}> + <Modal contentLabel={header} onRequestClose={this.props.onClose} size="small"> <form id="restore-profile-form" onSubmit={this.handleFormSubmit}> <div className="modal-head"> <h2>{header}</h2> @@ -93,24 +114,7 @@ export default class RestoreProfileForm extends React.PureComponent<Props, State <div className="modal-body"> {profile != null && ruleSuccesses != null ? ( - ruleFailures ? ( - <Alert variant="warning"> - {translateWithParameters( - 'quality_profiles.restore_profile.warning', - profile.name, - ruleSuccesses, - ruleFailures - )} - </Alert> - ) : ( - <Alert variant="success"> - {translateWithParameters( - 'quality_profiles.restore_profile.success', - profile.name, - ruleSuccesses - )} - </Alert> - ) + this.renderAlert(profile, ruleFailures, ruleSuccesses) ) : ( <div className="modal-field"> <label htmlFor="restore-profile-backup"> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/CreateProfileForm-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/CreateProfileForm-test.tsx new file mode 100644 index 00000000000..6d5c388e38b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/CreateProfileForm-test.tsx @@ -0,0 +1,43 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import CreateProfileForm from '../CreateProfileForm'; +import { mockQualityProfile } from '../../../../helpers/testMocks'; + +jest.mock('../../../../api/quality-profiles', () => ({ + changeProfileParent: jest.fn(), + createQualityProfile: jest.fn(), + getImporters: jest.fn().mockResolvedValue([]) +})); + +it('should render correctly', () => { + expect( + shallow( + <CreateProfileForm + languages={[{ key: 'kr', name: 'Hangeul' }]} + onClose={jest.fn()} + onCreate={jest.fn()} + organization="org" + profiles={[mockQualityProfile()]} + /> + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/RestoreProfileForm-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/RestoreProfileForm-test.tsx new file mode 100644 index 00000000000..f5c082b3d3b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/RestoreProfileForm-test.tsx @@ -0,0 +1,28 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import RestoreProfileForm from '../RestoreProfileForm'; + +it('should render correctly', () => { + expect( + shallow(<RestoreProfileForm onClose={jest.fn()} onRestore={jest.fn()} organization="org" />) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/CreateProfileForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/CreateProfileForm-test.tsx.snap new file mode 100644 index 00000000000..e4c75d36c7e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/CreateProfileForm-test.tsx.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<Modal + contentLabel="quality_profiles.new_profile" + onRequestClose={[MockFunction]} + size="small" +> + <form + id="create-profile-form" + onSubmit={[Function]} + > + <div + className="modal-head" + > + <h2> + quality_profiles.new_profile + </h2> + </div> + <div + className="modal-body" + > + <i + className="spinner" + /> + </div> + <div + className="modal-foot" + > + <ResetButtonLink + id="create-profile-cancel" + onClick={[MockFunction]} + > + cancel + </ResetButtonLink> + </div> + </form> +</Modal> +`; diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/RestoreProfileForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/RestoreProfileForm-test.tsx.snap new file mode 100644 index 00000000000..565666717c6 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/RestoreProfileForm-test.tsx.snap @@ -0,0 +1,62 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<Modal + contentLabel="quality_profiles.restore_profile" + onRequestClose={[MockFunction]} + size="small" +> + <form + id="restore-profile-form" + onSubmit={[Function]} + > + <div + className="modal-head" + > + <h2> + quality_profiles.restore_profile + </h2> + </div> + <div + className="modal-body" + > + <div + className="modal-field" + > + <label + htmlFor="restore-profile-backup" + > + backup + <em + className="mandatory" + > + * + </em> + </label> + <input + id="restore-profile-backup" + name="backup" + required={true} + type="file" + /> + </div> + </div> + <div + className="modal-foot" + > + <SubmitButton + disabled={false} + id="restore-profile-submit" + > + restore + </SubmitButton> + <ResetButtonLink + id="restore-profile-cancel" + onClick={[MockFunction]} + > + cancel + </ResetButtonLink> + </div> + </form> +</Modal> +`; diff --git a/server/sonar-web/src/main/js/apps/settings/components/EmailForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/EmailForm.tsx index 19ad53f17b2..278937d62d4 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/EmailForm.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/EmailForm.tsx @@ -18,12 +18,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { sendTestEmail } from '../../../api/settings'; -import { parseError } from '../../../helpers/request'; -import { SubmitButton } from '../../../components/ui/buttons'; +import DeferredSpinner from '../../../components/common/DeferredSpinner'; import { Alert } from '../../../components/ui/Alert'; +import { SubmitButton } from '../../../components/ui/buttons'; import { withCurrentUser } from '../../../components/hoc/withCurrentUser'; +import { sendTestEmail } from '../../../api/settings'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { parseError } from '../../../helpers/request'; interface Props { currentUser: T.LoggedInUser; @@ -38,7 +39,7 @@ interface State { error?: string; } -class EmailForm extends React.PureComponent<Props, State> { +export class EmailForm extends React.PureComponent<Props, State> { mounted = false; constructor(props: Props) { @@ -93,12 +94,16 @@ class EmailForm extends React.PureComponent<Props, State> { render() { return ( - <div className="huge-spacer-top"> - <h3 className="spacer-bottom">{translate('email_configuration.test.title')}</h3> - - <form onSubmit={this.handleFormSubmit} style={{ marginLeft: 201 }}> + <div className="settings-definition"> + <div className="settings-definition-left"> + <h3 className="settings-definition-name"> + {translate('email_configuration.test.title')} + </h3> + </div> + + <form className="settings-definition-right" onSubmit={this.handleFormSubmit}> {this.state.success && ( - <div className="modal-field"> + <div className="form-field"> <Alert variant="success"> {translateWithParameters( 'email_configuration.test.email_was_sent_to_x', @@ -109,12 +114,12 @@ class EmailForm extends React.PureComponent<Props, State> { )} {this.state.error != null && ( - <div className="modal-field"> + <div className="form-field"> <Alert variant="error">{this.state.error}</Alert> </div> )} - <div className="modal-field"> + <div className="form-field"> <label htmlFor="test-email-to"> {translate('email_configuration.test.to_address')} <em className="mandatory">*</em> @@ -129,7 +134,7 @@ class EmailForm extends React.PureComponent<Props, State> { value={this.state.recipient} /> </div> - <div className="modal-field"> + <div className="form-field"> <label htmlFor="test-email-subject"> {translate('email_configuration.test.subject')} </label> @@ -142,7 +147,7 @@ class EmailForm extends React.PureComponent<Props, State> { value={this.state.subject} /> </div> - <div className="modal-field"> + <div className="form-field"> <label htmlFor="test-email-message"> {translate('email_configuration.test.message')} <em className="mandatory">*</em> @@ -158,12 +163,10 @@ class EmailForm extends React.PureComponent<Props, State> { /> </div> - <div className="modal-field"> - {this.state.loading && <i className="spacer-right spinner" />} - <SubmitButton disabled={this.state.loading}> - {translate('email_configuration.test.send')} - </SubmitButton> - </div> + <SubmitButton disabled={this.state.loading}> + {translate('email_configuration.test.send')} + </SubmitButton> + {this.state.loading && <DeferredSpinner className="spacer-left" />} </form> </div> ); diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/EmailForm-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/EmailForm-test.tsx new file mode 100644 index 00000000000..6c714a8d248 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/EmailForm-test.tsx @@ -0,0 +1,27 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import { EmailForm } from '../EmailForm'; +import { mockCurrentUser } from '../../../../helpers/testMocks'; + +it('should render correctly', () => { + expect(shallow(<EmailForm currentUser={mockCurrentUser()} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/EmailForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/EmailForm-test.tsx.snap new file mode 100644 index 00000000000..cc15cb3104d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/EmailForm-test.tsx.snap @@ -0,0 +1,90 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<div + className="settings-definition" +> + <div + className="settings-definition-left" + > + <h3 + className="settings-definition-name" + > + email_configuration.test.title + </h3> + </div> + <form + className="settings-definition-right" + onSubmit={[Function]} + > + <div + className="form-field" + > + <label + htmlFor="test-email-to" + > + email_configuration.test.to_address + <em + className="mandatory" + > + * + </em> + </label> + <input + className="settings-large-input" + disabled={false} + id="test-email-to" + onChange={[Function]} + required={true} + type="email" + value="" + /> + </div> + <div + className="form-field" + > + <label + htmlFor="test-email-subject" + > + email_configuration.test.subject + </label> + <input + className="settings-large-input" + disabled={false} + id="test-email-subject" + onChange={[Function]} + type="text" + value="email_configuration.test.subject" + /> + </div> + <div + className="form-field" + > + <label + htmlFor="test-email-message" + > + email_configuration.test.message + <em + className="mandatory" + > + * + </em> + </label> + <textarea + className="settings-large-input" + disabled={false} + id="test-email-message" + onChange={[Function]} + required={true} + rows={5} + value="email_configuration.test.message_text" + /> + </div> + <SubmitButton + disabled={false} + > + email_configuration.test.send + </SubmitButton> + </form> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionForm.tsx b/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionForm.tsx index 57254c2fefa..8cfd77b3938 100644 --- a/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionForm.tsx +++ b/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionForm.tsx @@ -89,11 +89,11 @@ export default class EncryptionForm extends React.PureComponent<Props, State> { <form className="big-spacer-bottom" id="encryption-form" onSubmit={this.handleEncrypt}> <textarea autoFocus={true} - className="input-super-large" + className="abs-width-600" id="encryption-form-value" onChange={this.handleChange} required={true} - rows={3} + rows={5} value={this.state.value} /> <div className="spacer-top"> diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx index 1163399e78b..3051b630d83 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx @@ -32,18 +32,11 @@ export interface Props { currentUser: T.LoggedInUser; onClose: () => void; onOpenProjectOnboarding: () => void; - skipOnboarding: () => void; userOrganizations: T.Organization[]; } export function OnboardingModal(props: Props) { - const { - currentUser, - onClose, - onOpenProjectOnboarding, - skipOnboarding, - userOrganizations - } = props; + const { currentUser, onClose, onOpenProjectOnboarding, userOrganizations } = props; const organizations = userOrganizations.filter(o => o.key !== currentUser.personalOrganization); @@ -51,9 +44,9 @@ export function OnboardingModal(props: Props) { return ( <Modal contentLabel={header} - medium={true} onRequestClose={onClose} - shouldCloseOnOverlayClick={false}> + shouldCloseOnOverlayClick={false} + size={organizations.length > 0 ? 'medium' : 'small'}> <div className="modal-head"> <h2>{translate('onboarding.header')}</h2> <p className="spacer-top">{translate('onboarding.header.description')}</p> @@ -61,9 +54,7 @@ export function OnboardingModal(props: Props) { <div className="modal-body text-center display-flex-row huge-spacer-top huge-spacer-bottom"> <div className="flex-1"> <OnboardingProjectIcon className="big-spacer-bottom" /> - <h6 className="onboarding-choice-name big-spacer-bottom"> - {translate('onboarding.analyze_your_code')} - </h6> + <h3 className="big-spacer-bottom">{translate('onboarding.analyze_your_code')}</h3> <Button onClick={onOpenProjectOnboarding}> {translate('onboarding.project.create')} </Button> @@ -75,13 +66,10 @@ export function OnboardingModal(props: Props) { </div> <div className="flex-1"> <OnboardingTeamIcon className="big-spacer-bottom" /> - <h6 className="onboarding-choice-name big-spacer-bottom"> + <h3 className="big-spacer-bottom"> {translate('onboarding.browse_your_organizations')} - </h6> - <OrganizationsShortList - organizations={organizations} - skipOnboarding={skipOnboarding} - /> + </h3> + <OrganizationsShortList onClick={onClose} organizations={organizations} /> </div> </> )} diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingPage.tsx b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingPage.tsx index 5bda42d861b..52644d6b02e 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingPage.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingPage.tsx @@ -42,7 +42,6 @@ export class OnboardingPage extends React.PureComponent<Props> { <OnboardingModal onClose={this.closeOnboarding} onOpenProjectOnboarding={openProjectOnboarding} - skipOnboarding={this.props.skipOnboarding} /> )} </OnboardingContext.Consumer> diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationsShortList.tsx b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationsShortList.tsx index a7eecc85f6f..d7685c0cffb 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationsShortList.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationsShortList.tsx @@ -25,10 +25,10 @@ import { translate, translateWithParameters } from '../../../helpers/l10n'; export interface Props { organizations: T.Organization[]; - skipOnboarding: () => void; + onClick: () => void; } -export default function OrganizationsShortList({ organizations, skipOnboarding }: Props) { +export default function OrganizationsShortList({ onClick, organizations }: Props) { if (organizations.length === 0) { return null; } @@ -39,26 +39,27 @@ export default function OrganizationsShortList({ organizations, skipOnboarding } return ( <div> - <ul className="account-projects-list"> + <ul> {organizationsShown.map(organization => ( <li key={organization.key}> - <OrganizationsShortListItem - organization={organization} - skipOnboarding={skipOnboarding} - /> + <OrganizationsShortListItem onClick={onClick} organization={organization} /> </li> ))} </ul> - <div className="big-spacer-top"> - <span className="big-spacer-right"> - {translateWithParameters('x_of_y_shown', organizationsShown.length, organizations.length)} - </span> - {organizations.length > 3 && ( - <Link className="small" onClick={skipOnboarding} to="/account/organizations"> + {organizations.length > 3 && ( + <div className="big-spacer-top"> + <span className="big-spacer-right"> + {translateWithParameters( + 'x_of_y_shown', + organizationsShown.length, + organizations.length + )} + </span> + <Link className="small" onClick={onClick} to="/account/organizations"> {translate('see_all')} </Link> - )} - </div> + </div> + )} </div> ); } diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationsShortListItem.tsx b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationsShortListItem.tsx index 49be2296cb9..c9740ebd5e8 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationsShortListItem.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationsShortListItem.tsx @@ -24,15 +24,15 @@ import { withRouter, Router } from '../../../components/hoc/withRouter'; import { getOrganizationUrl } from '../../../helpers/urls'; interface Props { + onClick: () => void; organization: T.Organization; router: Router; - skipOnboarding: () => void; } export class OrganizationsShortListItem extends React.PureComponent<Props> { handleClick = () => { - const { organization, router, skipOnboarding } = this.props; - skipOnboarding(); + const { onClick, organization, router } = this.props; + onClick(); router.push(getOrganizationUrl(organization.key)); }; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OnboardingModal-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OnboardingModal-test.tsx index 2521fc0cba8..ab03de8c5d3 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OnboardingModal-test.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OnboardingModal-test.tsx @@ -59,7 +59,6 @@ function shallowRender(props: Partial<Props> = {}) { currentUser={mockCurrentUser()} onClose={jest.fn()} onOpenProjectOnboarding={jest.fn()} - skipOnboarding={jest.fn()} userOrganizations={[]} {...props} /> diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationsShortList-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationsShortList-test.tsx index f0e9b24e4d3..f5f46c188c7 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationsShortList-test.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationsShortList-test.tsx @@ -50,7 +50,5 @@ it('should limit displayed orgs to the first three', () => { }); function shallowRender(props: Partial<Props> = {}) { - return shallow( - <OrganizationsShortList organizations={[]} skipOnboarding={jest.fn()} {...props} /> - ); + return shallow(<OrganizationsShortList onClick={jest.fn()} organizations={[]} {...props} />); } diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationsShortListItem-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationsShortListItem-test.tsx index bf84c7895ba..acc2ba83e8f 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationsShortListItem-test.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationsShortListItem-test.tsx @@ -27,23 +27,23 @@ it('renders correctly', () => { expect(shallowRender()).toMatchSnapshot(); }); -it('calls skiponboarding and redirects to org page', () => { - const skipOnboarding = jest.fn(); +it('calls onClick and redirects to org page', () => { + const onClick = jest.fn(); const push = jest.fn(); - const wrapper = shallowRender({ skipOnboarding, router: mockRouter({ push }) }); + const wrapper = shallowRender({ onClick, router: mockRouter({ push }) }); click(wrapper); - expect(skipOnboarding).toHaveBeenCalledTimes(1); + expect(onClick).toHaveBeenCalledTimes(1); expect(push).toHaveBeenCalledWith('/organizations/foo'); }); function shallowRender(props: Partial<OrganizationsShortListItem['props']> = {}) { return shallow( <OrganizationsShortListItem + onClick={jest.fn()} organization={mockOrganization()} router={mockRouter()} - skipOnboarding={jest.fn()} {...props} /> ); diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OnboardingModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OnboardingModal-test.tsx.snap index 1c9eb96f30e..bd1ed0ad54f 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OnboardingModal-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OnboardingModal-test.tsx.snap @@ -3,9 +3,9 @@ exports[`renders correctly 1`] = ` <Modal contentLabel="onboarding.header" - medium={true} onRequestClose={[MockFunction]} shouldCloseOnOverlayClick={false} + size="small" > <div className="modal-head" @@ -28,11 +28,11 @@ exports[`renders correctly 1`] = ` <OnboardingProjectIcon className="big-spacer-bottom" /> - <h6 - className="onboarding-choice-name big-spacer-bottom" + <h3 + className="big-spacer-bottom" > onboarding.analyze_your_code - </h6> + </h3> <Button onClick={[MockFunction]} > @@ -55,9 +55,9 @@ exports[`renders correctly 1`] = ` exports[`should display organization list if any 1`] = ` <Modal contentLabel="onboarding.header" - medium={true} onRequestClose={[MockFunction]} shouldCloseOnOverlayClick={false} + size="medium" > <div className="modal-head" @@ -80,11 +80,11 @@ exports[`should display organization list if any 1`] = ` <OnboardingProjectIcon className="big-spacer-bottom" /> - <h6 - className="onboarding-choice-name big-spacer-bottom" + <h3 + className="big-spacer-bottom" > onboarding.analyze_your_code - </h6> + </h3> <Button onClick={[MockFunction]} > @@ -104,12 +104,13 @@ exports[`should display organization list if any 1`] = ` <OnboardingTeamIcon className="big-spacer-bottom" /> - <h6 - className="onboarding-choice-name big-spacer-bottom" + <h3 + className="big-spacer-bottom" > onboarding.browse_your_organizations - </h6> + </h3> <OrganizationsShortList + onClick={[MockFunction]} organizations={ Array [ Object { @@ -122,7 +123,6 @@ exports[`should display organization list if any 1`] = ` }, ] } - skipOnboarding={[MockFunction]} /> </div> </div> diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OrganizationsShortList-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OrganizationsShortList-test.tsx.snap index 5410482f395..fb1121921b8 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OrganizationsShortList-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OrganizationsShortList-test.tsx.snap @@ -2,46 +2,44 @@ exports[`should limit displayed orgs to the first three 1`] = ` <div> - <ul - className="account-projects-list" - > + <ul> <li key="bar" > <withRouter(OrganizationsShortListItem) + onClick={[MockFunction]} organization={ Object { "key": "bar", "name": "Bar", } } - skipOnboarding={[MockFunction]} /> </li> <li key="foo" > <withRouter(OrganizationsShortListItem) + onClick={[MockFunction]} organization={ Object { "key": "foo", "name": "Foo", } } - skipOnboarding={[MockFunction]} /> </li> <li key="kor" > <withRouter(OrganizationsShortListItem) + onClick={[MockFunction]} organization={ Object { "key": "kor", "name": "Kor", } } - skipOnboarding={[MockFunction]} /> </li> </ul> @@ -68,44 +66,33 @@ exports[`should limit displayed orgs to the first three 1`] = ` exports[`should render correctly 1`] = ` <div> - <ul - className="account-projects-list" - > + <ul> <li key="bar" > <withRouter(OrganizationsShortListItem) + onClick={[MockFunction]} organization={ Object { "key": "bar", "name": "Bar", } } - skipOnboarding={[MockFunction]} /> </li> <li key="foo" > <withRouter(OrganizationsShortListItem) + onClick={[MockFunction]} organization={ Object { "key": "foo", "name": "Foo", } } - skipOnboarding={[MockFunction]} /> </li> </ul> - <div - className="big-spacer-top" - > - <span - className="big-spacer-right" - > - x_of_y_shown.2.2 - </span> - </div> </div> `; diff --git a/server/sonar-web/src/main/js/apps/tutorials/styles.css b/server/sonar-web/src/main/js/apps/tutorials/styles.css index 41cf98f083d..2a7946f0f1c 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/styles.css +++ b/server/sonar-web/src/main/js/apps/tutorials/styles.css @@ -57,47 +57,3 @@ cursor: pointer; outline: none; } - -.onboarding-choices { - display: flex; - justify-content: space-around; - padding: 44px 170px; - background-color: var(--barBackgroundColor); - margin-top: var(--pagePadding); -} - -.onboarding-choice { - display: flex; - flex-direction: column; - justify-content: center; - padding: calc(2 * var(--gridSize)); - width: 190px; - height: 190px; - background-color: #fff; - border: solid 1px #fff; - border-radius: 3px; - transition: all 0.2s ease; - box-shadow: 0 1px 1px 1px var(--barBorderColor); -} - -.onboarding-choice svg { - color: var(--gray40); -} - -.onboarding-choice-name { - color: inherit; - font-size: var(--mediumFontSize); -} - -.onboarding-choice .note { - font-weight: 400; -} - -.onboarding-choice:hover, -.onboarding-choice:focus, -.onboarding-choice:active { - background-color: #fff; - color: var(--darkBlue); - box-shadow: var(--defaultShadow); - transform: translateY(-2px); -} 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 3bad28086b9..1e7b4cbe738 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 @@ -101,7 +101,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}> + <Modal contentLabel={header} onRequestClose={this.props.onClose} size="small"> <form autoComplete="off" id="user-password-form" onSubmit={this.handleChangePassword}> <header className="modal-head"> <h2>{header}</h2> 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 c62c2f253de..ef7b4103bf1 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 @@ -154,7 +154,7 @@ export default class TokensForm extends React.PureComponent<Props, State> { id="generate-token-form" onSubmit={this.handleGenerateToken}> <input - className="spacer-right" + className="input-large spacer-right" maxLength={100} onChange={this.handleNewTokenChange} placeholder={translate('users.enter_token_name')} 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 a96a27ea7c0..e5ec88fab7c 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 @@ -153,7 +153,7 @@ export default class UserForm extends React.PureComponent<Props, State> { const header = user ? translate('users.update_user') : translate('users.create_user'); return ( - <Modal contentLabel={header} onRequestClose={this.props.onClose}> + <Modal contentLabel={header} onRequestClose={this.props.onClose} size="small"> <form autoComplete="off" id="user-form" diff --git a/server/sonar-web/src/main/js/apps/users/components/UserScmAccountInput.tsx b/server/sonar-web/src/main/js/apps/users/components/UserScmAccountInput.tsx index 331e642bd8e..a39e72ec8e3 100644 --- a/server/sonar-web/src/main/js/apps/users/components/UserScmAccountInput.tsx +++ b/server/sonar-web/src/main/js/apps/users/components/UserScmAccountInput.tsx @@ -35,7 +35,7 @@ export default class UserScmAccountInput extends React.PureComponent<Props> { render() { return ( - <div className="js-scm-account"> + <div className="js-scm-account display-flex-row spacer-bottom"> <input maxLength={255} onChange={this.handleChange} diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/TokensForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/TokensForm-test.tsx.snap index 2a6ae4b6be1..401bae31b0a 100644 --- a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/TokensForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/TokensForm-test.tsx.snap @@ -14,7 +14,7 @@ exports[`should render correctly 1`] = ` onSubmit={[Function]} > <input - className="spacer-right" + className="input-large spacer-right" maxLength={100} onChange={[Function]} placeholder="users.enter_token_name" @@ -90,7 +90,7 @@ exports[`should render correctly 2`] = ` onSubmit={[Function]} > <input - className="spacer-right" + className="input-large spacer-right" maxLength={100} onChange={[Function]} placeholder="users.enter_token_name" diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/CreateWebhookForm.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/CreateWebhookForm.tsx index 0d43e06afd0..d8a830062c9 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/CreateWebhookForm.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/CreateWebhookForm.tsx @@ -74,6 +74,7 @@ export default class CreateWebhookForm extends React.PureComponent<Props> { isInitialValid={isUpdate} onClose={this.props.onClose} onSubmit={this.props.onDone} + size="small" validate={this.handleValidate}> {({ dirty, diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/CreateWebhookForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/CreateWebhookForm-test.tsx.snap index 489d1aecb6f..b9b3eda1ca3 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/CreateWebhookForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/CreateWebhookForm-test.tsx.snap @@ -13,6 +13,7 @@ exports[`should render correctly when creating a new webhook 1`] = ` isInitialValid={false} onClose={[MockFunction]} onSubmit={[MockFunction]} + size="small" validate={[Function]} > <Component /> @@ -32,6 +33,7 @@ exports[`should render correctly when updating a webhook 1`] = ` isInitialValid={true} onClose={[MockFunction]} onSubmit={[MockFunction]} + size="small" validate={[Function]} > <Component /> diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx index e34b22faf4d..9f6834cfa65 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx @@ -388,7 +388,7 @@ export default class MeasuresOverlay extends React.PureComponent<Props, State> { const { loading } = this.state; return ( - <Modal contentLabel="" large={true} onRequestClose={this.props.onClose}> + <Modal contentLabel="" onRequestClose={this.props.onClose} size={'large'}> <div className="modal-container source-viewer-measures-modal"> <div className="source-viewer-header-component source-viewer-measures-component"> <div className="source-viewer-header-component-project"> diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlay-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlay-test.tsx.snap index 71583aa014b..fb658fc95a2 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlay-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlay-test.tsx.snap @@ -3,8 +3,8 @@ exports[`should render source file 1`] = ` <Modal contentLabel="" - large={true} onRequestClose={[MockFunction]} + size="large" > <div className="modal-container source-viewer-measures-modal" @@ -362,8 +362,8 @@ exports[`should render source file 1`] = ` exports[`should render source file 2`] = ` <Modal contentLabel="" - large={true} onRequestClose={[MockFunction]} + size="large" > <div className="modal-container source-viewer-measures-modal" @@ -1327,8 +1327,8 @@ exports[`should render source file 2`] = ` exports[`should render test file 1`] = ` <Modal contentLabel="" - large={true} onRequestClose={[MockFunction]} + size="large" > <div className="modal-container source-viewer-measures-modal" diff --git a/server/sonar-web/src/main/js/components/common/MarkdownTips.tsx b/server/sonar-web/src/main/js/components/common/MarkdownTips.tsx index 1007498f19b..2c99b2f74a4 100644 --- a/server/sonar-web/src/main/js/components/common/MarkdownTips.tsx +++ b/server/sonar-web/src/main/js/components/common/MarkdownTips.tsx @@ -18,10 +18,15 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import * as classNames from 'classnames'; import { getMarkdownHelpUrl } from '../../helpers/urls'; import { translate } from '../../helpers/l10n'; -export default class MarkdownTips extends React.PureComponent { +interface Props { + className?: string; +} + +export default class MarkdownTips extends React.PureComponent<Props> { handleClick(evt: React.SyntheticEvent<HTMLAnchorElement>) { evt.preventDefault(); window.open(getMarkdownHelpUrl(), 'Markdown', 'height=300,width=600,scrollbars=1,resizable=1'); @@ -29,7 +34,7 @@ export default class MarkdownTips extends React.PureComponent { render() { return ( - <div className="markdown-tips"> + <div className={classNames('markdown-tips', this.props.className)}> <a className="little-spacer-right" href="#" onClick={this.handleClick}> {translate('markdown.helplink')} </a> diff --git a/server/sonar-web/src/main/js/components/controls/Checkbox.tsx b/server/sonar-web/src/main/js/components/controls/Checkbox.tsx index e9e6a94f4b4..a1df816ed01 100644 --- a/server/sonar-web/src/main/js/components/controls/Checkbox.tsx +++ b/server/sonar-web/src/main/js/components/controls/Checkbox.tsx @@ -29,6 +29,7 @@ interface Props { id?: string; loading?: boolean; onCheck: (checked: boolean, id?: string) => void; + right?: boolean; thirdState?: boolean; } @@ -46,31 +47,33 @@ export default class Checkbox extends React.PureComponent<Props> { }; render() { + const { children, disabled, loading, right } = this.props; const className = classNames('icon-checkbox', { 'icon-checkbox-checked': this.props.checked, 'icon-checkbox-single': this.props.thirdState, - 'icon-checkbox-disabled': this.props.disabled + 'icon-checkbox-disabled': disabled }); - if (this.props.children) { + if (children) { return ( <a className={classNames('link-checkbox', this.props.className, { - note: this.props.disabled, - disabled: this.props.disabled + note: disabled, + disabled })} href="#" id={this.props.id} onClick={this.handleClick}> - <DeferredSpinner loading={Boolean(this.props.loading)}> + {right && children} + <DeferredSpinner loading={Boolean(loading)}> <i className={className} /> </DeferredSpinner> - {this.props.children} + {!right && children} </a> ); } - if (this.props.loading) { + if (loading) { return <DeferredSpinner />; } diff --git a/server/sonar-web/src/main/js/components/controls/ConfirmModal.tsx b/server/sonar-web/src/main/js/components/controls/ConfirmModal.tsx index a86ac592a73..2b018709630 100644 --- a/server/sonar-web/src/main/js/components/controls/ConfirmModal.tsx +++ b/server/sonar-web/src/main/js/components/controls/ConfirmModal.tsx @@ -94,8 +94,8 @@ export default class ConfirmModal<T = string> extends React.PureComponent<Props< }; render() { - const { header, onClose, medium, noBackdrop, large } = this.props; - const modalProps = { header, onClose, medium, noBackdrop, large }; + const { header, onClose, noBackdrop, size } = this.props; + const modalProps = { header, onClose, noBackdrop, size }; return ( <SimpleModal onSubmit={this.handleSubmit} {...modalProps}> {this.renderModalContent} diff --git a/server/sonar-web/src/main/js/components/controls/Modal.tsx b/server/sonar-web/src/main/js/components/controls/Modal.tsx index e3beac1109b..c250470f9af 100644 --- a/server/sonar-web/src/main/js/components/controls/Modal.tsx +++ b/server/sonar-web/src/main/js/components/controls/Modal.tsx @@ -20,15 +20,13 @@ import * as React from 'react'; import * as ReactModal from 'react-modal'; import * as classNames from 'classnames'; -import { isSonarCloud } from '../../helpers/system'; ReactModal.setAppElement('#content'); export interface ModalProps { children: React.ReactNode; - medium?: boolean; + size?: 'small' | 'medium' | 'large'; noBackdrop?: boolean; - large?: boolean; } type MandatoryProps = Pick<ReactModal.Props, 'contentLabel'>; @@ -38,16 +36,11 @@ type Props = Partial<ReactModal.Props> & MandatoryProps & ModalProps; export default function Modal(props: Props) { return ( <ReactModal - className={classNames( - 'modal', - { - sonarcloud: isSonarCloud() - }, - { - 'modal-medium': props.medium, - 'modal-large': props.large - } - )} + className={classNames('modal', { + 'modal-small': props.size === 'small', + 'modal-medium': props.size === 'medium', + 'modal-large': props.size === 'large' + })} isOpen={true} overlayClassName={classNames('modal-overlay', { 'modal-no-backdrop': props.noBackdrop })} {...props} diff --git a/server/sonar-web/src/main/js/components/controls/Radio.tsx b/server/sonar-web/src/main/js/components/controls/Radio.tsx index e389c71d664..ba030d96b61 100644 --- a/server/sonar-web/src/main/js/components/controls/Radio.tsx +++ b/server/sonar-web/src/main/js/components/controls/Radio.tsx @@ -38,10 +38,7 @@ export default class Radio extends React.PureComponent<Props> { return ( <a aria-checked={this.props.checked} - className={classNames( - 'display-inline-flex-center link-base-color link-no-underline', - this.props.className - )} + className={classNames('display-inline-flex-center link-checkbox', this.props.className)} href="#" onClick={this.handleClick} role="radio"> diff --git a/server/sonar-web/src/main/js/components/controls/SearchSelect.tsx b/server/sonar-web/src/main/js/components/controls/SearchSelect.tsx index 3a78bd4ea42..43558ccdf90 100644 --- a/server/sonar-web/src/main/js/components/controls/SearchSelect.tsx +++ b/server/sonar-web/src/main/js/components/controls/SearchSelect.tsx @@ -25,6 +25,7 @@ import { translate, translateWithParameters } from '../../helpers/l10n'; interface Props<T> { autofocus?: boolean; canCreate?: boolean; + className?: string; clearable?: boolean; defaultOptions?: T[]; minimumQueryLength?: number; @@ -126,7 +127,7 @@ export default class SearchSelect<T extends { value: string }> extends React.Pur return ( <Component autoFocus={this.autofocus} - className="input-super-large" + className={this.props.className} clearable={this.props.clearable} escapeClearsValue={false} filterOption={this.handleFilterOption} diff --git a/server/sonar-web/src/main/js/components/controls/ValidationModal.tsx b/server/sonar-web/src/main/js/components/controls/ValidationModal.tsx index d4138956752..d359d8daebf 100644 --- a/server/sonar-web/src/main/js/components/controls/ValidationModal.tsx +++ b/server/sonar-web/src/main/js/components/controls/ValidationModal.tsx @@ -18,13 +18,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import Modal from './Modal'; +import Modal, { ModalProps } from './Modal'; import ValidationForm, { ChildrenProps } from './ValidationForm'; import DeferredSpinner from '../common/DeferredSpinner'; import { SubmitButton, ResetButtonLink } from '../ui/buttons'; import { translate } from '../../helpers/l10n'; -interface Props<V> { +interface Props<V> extends ModalProps { children: (props: ChildrenProps<V>) => React.ReactNode; confirmButtonText: string; header: string; @@ -44,7 +44,11 @@ export default class ValidationModal<V> extends React.PureComponent<Props<V>> { render() { return ( - <Modal contentLabel={this.props.header} onRequestClose={this.props.onClose}> + <Modal + contentLabel={this.props.header} + noBackdrop={this.props.noBackdrop} + onRequestClose={this.props.onClose} + size={this.props.size}> <ValidationForm initialValues={this.props.initialValues} isInitialValid={this.props.isInitialValid} diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/Checkbox-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/Checkbox-test.tsx index a0985d44a84..f089b43dfa5 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/Checkbox-test.tsx +++ b/server/sonar-web/src/main/js/components/controls/__tests__/Checkbox-test.tsx @@ -102,3 +102,7 @@ it('should apply custom class', () => { ); expect(checkbox.is('.customclass')).toBeTruthy(); }); + +it('should render the checkbox on the right', () => { + expect(shallow(<Checkbox checked={true} onCheck={() => true} right={true} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Checkbox-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Checkbox-test.tsx.snap new file mode 100644 index 00000000000..f733f88a916 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Checkbox-test.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render the checkbox on the right 1`] = ` +<a + className="icon-checkbox icon-checkbox-checked" + href="#" + onClick={[Function]} +/> +`; diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Radio-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Radio-test.tsx.snap index 92e8076ce5e..cd6bc65b080 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Radio-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Radio-test.tsx.snap @@ -3,7 +3,7 @@ exports[`should render and check 1`] = ` <a aria-checked={false} - className="display-inline-flex-center link-base-color link-no-underline" + className="display-inline-flex-center link-checkbox" href="#" onClick={[Function]} role="radio" @@ -17,7 +17,7 @@ exports[`should render and check 1`] = ` exports[`should render and check 2`] = ` <a aria-checked={true} - className="display-inline-flex-center link-base-color link-no-underline" + className="display-inline-flex-center link-checkbox" href="#" onClick={[Function]} role="radio" diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/SearchSelect-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/SearchSelect-test.tsx.snap index 9f99087e010..792343f1a73 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/SearchSelect-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/SearchSelect-test.tsx.snap @@ -3,7 +3,6 @@ exports[`should render Select 1`] = ` <Select autoFocus={true} - className="input-super-large" escapeClearsValue={false} filterOption={[Function]} isLoading={false} diff --git a/server/sonar-web/src/main/js/components/icons-components/OnboardingAddMembersIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/OnboardingAddMembersIcon.tsx index 0d6371cfaef..f0e0f2db583 100644 --- a/server/sonar-web/src/main/js/components/icons-components/OnboardingAddMembersIcon.tsx +++ b/server/sonar-web/src/main/js/components/icons-components/OnboardingAddMembersIcon.tsx @@ -19,10 +19,11 @@ */ import * as React from 'react'; import Icon, { IconProps } from './Icon'; +import * as theme from '../../app/theme'; export default function OnboardingAddMembersIcon({ className, - fill = 'currentColor', + fill = theme.darkBlue, size = 64 }: IconProps) { return ( diff --git a/server/sonar-web/src/main/js/components/ui/NewInfoBox.tsx b/server/sonar-web/src/main/js/components/ui/NewInfoBox.tsx deleted file mode 100644 index c9a2da3e401..00000000000 --- a/server/sonar-web/src/main/js/components/ui/NewInfoBox.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import * as classNames from 'classnames'; -import { ButtonIcon } from './buttons'; -import ClearIcon from '../icons-components/ClearIcon'; -import { sonarcloudBlack500 } from '../../app/theme'; -import { translate } from '../../helpers/l10n'; -import './NewInfoBox.css'; - -export interface Props { - children: React.ReactNode; - className?: string; - description: React.ReactNode; - onClose?: () => void; - title: string; -} - -export default function NewInfoBox({ className, children, description, onClose, title }: Props) { - return ( - <div className={classNames('new-info-box', className)} role="alert"> - <div className="new-info-box-inner text-left"> - <div className="new-info-box-header spacer-bottom"> - <span className="display-inline-flex-center"> - <span className="badge badge-new spacer-right">{translate('new')}</span> - <strong>{title}</strong> - </span> - </div> - <p className="note spacer-bottom">{description}</p> - {children} - </div> - <ButtonIcon className="button-small spacer-left" color={sonarcloudBlack500} onClick={onClose}> - <ClearIcon size={12} /> - </ButtonIcon> - </div> - ); -} diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/NewInfoBox-test.tsx.snap b/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/NewInfoBox-test.tsx.snap deleted file mode 100644 index fca669ba00a..00000000000 --- a/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/NewInfoBox-test.tsx.snap +++ /dev/null @@ -1,44 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -<div - className="new-info-box" - role="alert" -> - <div - className="new-info-box-inner text-left" - > - <div - className="new-info-box-header spacer-bottom" - > - <span - className="display-inline-flex-center" - > - <span - className="badge badge-new spacer-right" - > - new - </span> - <strong> - My title - </strong> - </span> - </div> - <p - className="note spacer-bottom" - > - My description - </p> - <div /> - </div> - <ButtonIcon - className="button-small spacer-left" - color="#8a8c8f" - onClick={[MockFunction]} - > - <ClearIcon - size={12} - /> - </ButtonIcon> -</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 cede4ec4d10..93b755a2230 100644 --- a/server/sonar-web/src/main/js/components/ui/buttons.css +++ b/server/sonar-web/src/main/js/components/ui/buttons.css @@ -174,6 +174,27 @@ font-size: var(--mediumFontSize); } +.button-huge { + flex-direction: column; + padding: calc(2 * var(--gridSize)); + width: 200px; + height: 200px; + background-color: #fff; + border: solid 1px #fff; + border-radius: 3px; + transition: all 0.2s ease; + box-shadow: 0 1px 1px 1px var(--barBorderColor); +} + +.button-huge:hover, +.button-huge:focus, +.button-huge:active { + background-color: #fff; + color: var(--darkBlue); + box-shadow: var(--defaultShadow); + transform: translateY(-2px); +} + /* #region .button-group */ .button-group { display: inline-block; @@ -271,12 +292,12 @@ /* #endregion */ .button-list { + display: inline-flex; + justify-content: space-between; height: auto; border: 1px solid var(--barBorderColor); padding: var(--gridSize); margin: calc(var(--gridSize) / 2); - text-align: left; - justify-content: space-between; color: var(--secondFontColor); font-weight: normal; } @@ -285,5 +306,4 @@ background-color: white; border-color: var(--blue); color: var(--darkBlue); - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.175); } 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 99e4c327a62..5ad0b9197ef 100644 --- a/server/sonar-web/src/main/js/components/ui/buttons.tsx +++ b/server/sonar-web/src/main/js/components/ui/buttons.tsx @@ -20,11 +20,11 @@ import * as React from 'react'; import * as classNames from 'classnames'; import * as theme from '../../app/theme'; +import ChevronRightIcon from '../icons-components/ChevronRightcon'; import ClearIcon from '../icons-components/ClearIcon'; import EditIcon from '../icons-components/EditIcon'; import Tooltip from '../controls/Tooltip'; import './buttons.css'; -import ChevronRightIcon from '../icons-components/ChevronRightcon'; type AllowedButtonAttributes = Pick< React.ButtonHTMLAttributes<HTMLButtonElement>, diff --git a/server/sonar-web/src/main/js/helpers/testMocks.ts b/server/sonar-web/src/main/js/helpers/testMocks.ts index bd37528c205..a6e20a88178 100644 --- a/server/sonar-web/src/main/js/helpers/testMocks.ts +++ b/server/sonar-web/src/main/js/helpers/testMocks.ts @@ -21,6 +21,17 @@ import { InjectedRouter } from 'react-router'; import { Location } from 'history'; import { Profile } from '../apps/quality-profiles/types'; +export function mockAlmApplication(overrides: Partial<T.AlmApplication> = {}): T.AlmApplication { + return { + backgroundColor: '#0052CC', + iconPath: '"/static/authbitbucket/bitbucket.svg"', + installationUrl: 'https://bitbucket.org/install/app', + key: 'bitbucket', + name: 'BitBucket', + ...overrides + }; +} + export function mockAlmOrganization(overrides: Partial<T.AlmOrganization> = {}): T.AlmOrganization { return { avatar: 'http://example.com/avatar', @@ -90,6 +101,52 @@ export function mockEvent(overrides = {}) { } as any; } +export function mockIssue(withLocations = false, overrides: Partial<T.Issue> = {}) { + const issue: T.Issue = { + actions: [], + component: 'main.js', + componentLongName: 'main.js', + componentQualifier: 'FIL', + componentUuid: 'foo1234', + creationDate: '2017-03-01T09:36:01+0100', + flows: [], + fromHotspot: false, + key: 'AVsae-CQS-9G3txfbFN2', + line: 25, + message: 'Reduce the number of conditional operators (4) used in the expression', + organization: 'myorg', + project: 'myproject', + projectKey: 'foo', + projectName: 'Foo', + projectOrganization: 'org', + rule: 'javascript:S1067', + ruleName: 'foo', + secondaryLocations: [], + severity: 'MAJOR', + status: 'OPEN', + textRange: { startLine: 25, endLine: 26, startOffset: 0, endOffset: 15 }, + transitions: [], + type: 'BUG' + }; + + function loc(): T.FlowLocation { + return { + component: 'main.js', + textRange: { startLine: 1, startOffset: 1, endLine: 2, endOffset: 2 } + }; + } + + if (withLocations) { + issue.flows = [[loc(), loc(), loc()], [loc(), loc()]]; + issue.secondaryLocations = [loc(), loc()]; + } + + return { + ...issue, + ...overrides + }; +} + export function mockLocation(overrides: Partial<Location> = {}): Location { return { action: 'PUSH', @@ -123,6 +180,14 @@ export function mockOrganizationWithAlm( }); } +export function mockQualityGate(overrides: Partial<T.QualityGate> = {}): T.QualityGate { + return { + id: 1, + name: 'qualitygate', + ...overrides + }; +} + export function mockQualityProfile(overrides: Partial<Profile> = {}): Profile { return { activeDeprecatedRuleCount: 2, @@ -157,48 +222,17 @@ export function mockRouter(overrides: { push?: Function; replace?: Function } = } as InjectedRouter; } -export function mockIssue(withLocations = false, overrides: Partial<T.Issue> = {}) { - const issue: T.Issue = { - actions: [], - component: 'main.js', - componentLongName: 'main.js', - componentQualifier: 'FIL', - componentUuid: 'foo1234', - creationDate: '2017-03-01T09:36:01+0100', - flows: [], - fromHotspot: false, - key: 'AVsae-CQS-9G3txfbFN2', - line: 25, - message: 'Reduce the number of conditional operators (4) used in the expression', - organization: 'myorg', - project: 'myproject', - projectKey: 'foo', - projectName: 'Foo', - projectOrganization: 'org', - rule: 'javascript:S1067', - ruleName: 'foo', - secondaryLocations: [], - severity: 'MAJOR', - status: 'OPEN', - textRange: { startLine: 25, endLine: 26, startOffset: 0, endOffset: 15 }, - transitions: [], - type: 'BUG' - }; - - function loc(): T.FlowLocation { - return { - component: 'main.js', - textRange: { startLine: 1, startOffset: 1, endLine: 2, endOffset: 2 } - }; - } - - if (withLocations) { - issue.flows = [[loc(), loc(), loc()], [loc(), loc()]]; - issue.secondaryLocations = [loc(), loc()]; - } - +export function mockRule(overrides: Partial<T.Rule> = {}): T.Rule { return { - ...issue, + key: 'javascript:S1067', + lang: 'js', + langName: 'JavaScript', + name: 'Use foo', + severity: 'MAJOR', + status: 'READY', + sysTags: ['a', 'b'], + tags: ['x'], + type: 'CODE_SMELL', ...overrides - }; + } as T.Rule; } |