* Update modal-fields * Update form-fields styling * Update modal-fields usage in extensions * Clean csstags/7.7
@@ -57,7 +57,7 @@ public class SetSettingAction implements UsersWsAction { | |||
.setRequired(true) | |||
.setMaximumLength(100) | |||
.setDescription("Setting key") | |||
.setPossibleValues("notifications.optOut", UserUpdater.NOTIFICATIONS_READ_DATE, "organizations.members.dismissSyncNotif"); | |||
.setPossibleValues("notifications.optOut", UserUpdater.NOTIFICATIONS_READ_DATE); | |||
action.createParam(PARAM_VALUE) | |||
.setRequired(true) |
@@ -91,17 +91,11 @@ public class SetSettingActionTest { | |||
.setParam("value", "true") | |||
.execute(); | |||
ws.newRequest() | |||
.setParam("key", "organizations.members.dismissSyncNotif") | |||
.setParam("value", "org1,org2") | |||
.execute(); | |||
assertThat(db.getDbClient().userPropertiesDao().selectByUser(db.getSession(), user)) | |||
.extracting(UserPropertyDto::getKey, UserPropertyDto::getValue) | |||
.containsExactlyInAnyOrder( | |||
tuple("notifications.readDate", "1234"), | |||
tuple("notifications.optOut", "true"), | |||
tuple("organizations.members.dismissSyncNotif", "org1,org2")); | |||
tuple("notifications.optOut", "true")); | |||
} | |||
@Test | |||
@@ -131,8 +125,7 @@ public class SetSettingActionTest { | |||
assertThat(definition.param("key").possibleValues()).containsExactlyInAnyOrder( | |||
"notifications.optOut", | |||
"notifications.readDate", | |||
"organizations.members.dismissSyncNotif"); | |||
"notifications.readDate"); | |||
} | |||
} |
@@ -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> |
@@ -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, |
@@ -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, |
@@ -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 { |
@@ -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 { |
@@ -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; |
@@ -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> |
@@ -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(); | |||
}); |
@@ -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> | |||
`; |
@@ -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')} |
@@ -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')} |
@@ -3,8 +3,8 @@ | |||
exports[`renders 1`] = ` | |||
<Modal | |||
contentLabel="scanner context" | |||
large={true} | |||
onRequestClose={[MockFunction]} | |||
size="large" | |||
> | |||
<div | |||
className="modal-head" |
@@ -3,8 +3,8 @@ | |||
exports[`renders 1`] = ` | |||
<Modal | |||
contentLabel="stacktrace" | |||
large={true} | |||
onRequestClose={[MockFunction]} | |||
size="large" | |||
> | |||
<div | |||
className="modal-head" |
@@ -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} |
@@ -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() | |||
)} |
@@ -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"> |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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(); | |||
}); |
@@ -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(); | |||
}); |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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} | |||
/> |
@@ -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> | |||
`; |
@@ -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> | |||
`; |
@@ -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> | |||
`; |
@@ -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", |
@@ -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; | |||
} |
@@ -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> |
@@ -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" |
@@ -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> | |||
} |
@@ -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)) |
@@ -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()} |
@@ -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", |
@@ -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 |
@@ -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; |
@@ -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; | |||
}); | |||
}; | |||
} |
@@ -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"> |
@@ -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]} |
@@ -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"> |
@@ -4,6 +4,7 @@ exports[`should render form 1`] = ` | |||
<Modal | |||
contentLabel="header" | |||
onRequestClose={[MockFunction]} | |||
size="small" | |||
> | |||
<form | |||
onSubmit={[Function]} |
@@ -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"> |
@@ -4,6 +4,7 @@ exports[`should render form 1`] = ` | |||
<Modal | |||
contentLabel="header" | |||
onRequestClose={[MockFunction]} | |||
size="small" | |||
> | |||
<form | |||
onSubmit={[Function]} |
@@ -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> | |||
); |
@@ -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" |
@@ -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); | |||
} |
@@ -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} |
@@ -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> | |||
); | |||
} | |||
} |
@@ -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} | |||
/> | |||
))} |
@@ -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', |
@@ -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> | |||
); |
@@ -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); |
@@ -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 && ( |
@@ -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> | |||
); | |||
} |
@@ -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(); | |||
}); |
@@ -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(); | |||
}); | |||
} |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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} | |||
/> | |||
); |
@@ -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 |
@@ -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 () => { |
@@ -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 |
@@ -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> |
@@ -33,7 +33,6 @@ exports[`should render a list of members of an organization 1`] = ` | |||
} | |||
} | |||
organizationGroups={Array []} | |||
removeMember={[MockFunction]} | |||
updateMemberGroups={[MockFunction]} | |||
/> | |||
<MembersListItem |
@@ -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 |
@@ -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" |
@@ -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> | |||
`; |
@@ -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 { |
@@ -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> |
@@ -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> |
@@ -19,5 +19,5 @@ | |||
*/ | |||
.organization-empty { | |||
margin: 100px auto 0; | |||
width: 800px; | |||
width: 472px; | |||
} |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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"> |
@@ -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(); | |||
}); |
@@ -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> | |||
`; |
@@ -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"> |
@@ -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(); | |||
}); |
@@ -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> | |||
`; |
@@ -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 |
@@ -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} /> |
@@ -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(); | |||
}); |
@@ -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(); | |||
}); |
@@ -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> | |||
`; |
@@ -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> | |||
`; |
@@ -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> |
@@ -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> | |||
)} |
@@ -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" |
@@ -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> |
@@ -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"> |
@@ -4,6 +4,7 @@ exports[`should create link 1`] = ` | |||
<Modal | |||
contentLabel="project_links.create_new_project_link" | |||
onRequestClose={[MockFunction]} | |||
size="small" | |||
> | |||
<form | |||
onSubmit={[Function]} |
@@ -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> |
@@ -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} |
@@ -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" |
@@ -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={ |
@@ -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> |
@@ -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> | |||
); | |||
} | |||
} | |||
} |