]> source.dussan.org Git - sonarqube.git/commitdiff
SONARCLOUD-380 Rework modal styling of SC and SQ
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Fri, 15 Feb 2019 16:52:52 +0000 (17:52 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 6 Mar 2019 10:30:42 +0000 (11:30 +0100)
* Update modal-fields
* Update form-fields styling
* Update modal-fields usage in extensions
* Clean css

172 files changed:
server/sonar-server/src/main/java/org/sonar/server/user/ws/SetSettingAction.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/SetSettingActionTest.java
server/sonar-web/src/main/js/app/components/StartupModal.tsx
server/sonar-web/src/main/js/app/styles/components/modals.css
server/sonar-web/src/main/js/app/styles/init/base.css
server/sonar-web/src/main/js/app/styles/init/forms.css
server/sonar-web/src/main/js/app/styles/init/type.css
server/sonar-web/src/main/js/app/types.d.ts
server/sonar-web/src/main/js/apps/account/components/Password.tsx
server/sonar-web/src/main/js/apps/account/components/__tests__/Password-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/account/components/__tests__/__snapshots__/Password-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/components/ScannerContext.tsx
server/sonar-web/src/main/js/apps/background-tasks/components/Stacktrace.tsx
server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/ScannerContext-test.tsx.snap
server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/Stacktrace-test.tsx.snap
server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormModal.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsCustomRules.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsDescription.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsProfiles.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/ActivationFormModal-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/BulkChangeModal-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/CustomRuleFormModal-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleListItem-test.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/ActivationFormModal-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/CustomRuleFormModal-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleListItem-test.tsx.snap
server/sonar-web/src/main/js/apps/coding-rules/styles.css
server/sonar-web/src/main/js/apps/create/components/UpgradeOrganizationModal.tsx
server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/UpgradeOrganizationModal-test.tsx.snap
server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx
server/sonar-web/src/main/js/apps/create/organization/RemoteOrganizationChoose.tsx
server/sonar-web/src/main/js/apps/create/organization/__tests__/AutoOrganizationCreate-test.tsx
server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AutoOrganizationCreate-test.tsx.snap
server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/RemoteOrganizationChoose-test.tsx.snap
server/sonar-web/src/main/js/apps/create/organization/__tests__/actions-test.ts
server/sonar-web/src/main/js/apps/create/organization/actions.ts
server/sonar-web/src/main/js/apps/custom-measures/components/Form.tsx
server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/Form-test.tsx.snap
server/sonar-web/src/main/js/apps/custom-metrics/components/Form.tsx
server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/Form-test.tsx.snap
server/sonar-web/src/main/js/apps/groups/components/Form.tsx
server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Form-test.tsx.snap
server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx
server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap
server/sonar-web/src/main/js/apps/issues/styles.css
server/sonar-web/src/main/js/apps/organizationMembers/AddMemberForm.tsx
server/sonar-web/src/main/js/apps/organizationMembers/ManageMemberGroupsForm.tsx
server/sonar-web/src/main/js/apps/organizationMembers/MembersList.tsx
server/sonar-web/src/main/js/apps/organizationMembers/MembersListHeader.tsx
server/sonar-web/src/main/js/apps/organizationMembers/MembersListItem.tsx
server/sonar-web/src/main/js/apps/organizationMembers/MembersPageHeader.tsx
server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx
server/sonar-web/src/main/js/apps/organizationMembers/SyncMemberForm.tsx
server/sonar-web/src/main/js/apps/organizationMembers/__tests__/ManageMemberGroupsForm-test.tsx
server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersList-test.tsx
server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListItem-test.tsx
server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersPageHeader-test.tsx
server/sonar-web/src/main/js/apps/organizationMembers/__tests__/OrganizationMembers-test.tsx
server/sonar-web/src/main/js/apps/organizationMembers/__tests__/SyncMemberForm-test.tsx
server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/AddMemberForm-test.tsx.snap
server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap
server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersList-test.tsx.snap
server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListHeader-test.tsx.snap
server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListItem-test.tsx.snap
server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersPageHeader-test.tsx.snap
server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/OrganizationMembers-test.tsx.snap
server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/SyncMemberForm-test.tsx.snap
server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.tsx
server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.css
server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.tsx
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.tsx.snap
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEmpty-test.tsx.snap
server/sonar-web/src/main/js/apps/permission-templates/components/Form.tsx
server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/Form-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/Form-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/permissions/project/components/ApplyTemplate.tsx
server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/ApplyTemplate-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/__snapshots__/ApplyTemplate-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.tsx
server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeEventForm.tsx
server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/AddEventForm-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/ChangeEventForm-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/__snapshots__/AddEventForm-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/__snapshots__/ChangeEventForm-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectBranches/components/RenameBranchModal.tsx
server/sonar-web/src/main/js/apps/projectBranches/components/SettingForm.tsx
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/RenameBranchModal-test.tsx.snap
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/SettingForm-test.tsx.snap
server/sonar-web/src/main/js/apps/projectLinks/CreationModal.tsx
server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/CreationModal-test.tsx.snap
server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx
server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/BulkApplyTemplateModal-test.tsx.snap
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap
server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/ConditionOperator.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/CreateQualityGateForm.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/RenameQualityGateForm.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionModal-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionOperator-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/CopyQualityGateForm-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/CreateQualityGateForm-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/MetricSelect-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/RenameQualityGateForm-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionModal-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionOperator-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/CopyQualityGateForm-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/CreateQualityGateForm-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/MetricSelect-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/RenameQualityGateForm-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/components/CopyProfileForm.tsx
server/sonar-web/src/main/js/apps/quality-profiles/components/ExtendProfileForm.tsx
server/sonar-web/src/main/js/apps/quality-profiles/components/RenameProfileForm.tsx
server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/ExtendProfileForm-test.tsx.snap
server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.tsx
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissionsForm.tsx
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfilePermissionsFormSelect.tsx
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfilePermissionsForm-test.tsx.snap
server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx
server/sonar-web/src/main/js/apps/quality-profiles/home/RestoreProfileForm.tsx
server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/CreateProfileForm-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/RestoreProfileForm-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/CreateProfileForm-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/RestoreProfileForm-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/EmailForm.tsx
server/sonar-web/src/main/js/apps/settings/components/__tests__/EmailForm-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/EmailForm-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/encryption/EncryptionForm.tsx
server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx
server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingPage.tsx
server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationsShortList.tsx
server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationsShortListItem.tsx
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OnboardingModal-test.tsx
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationsShortList-test.tsx
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationsShortListItem-test.tsx
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OnboardingModal-test.tsx.snap
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OrganizationsShortList-test.tsx.snap
server/sonar-web/src/main/js/apps/tutorials/styles.css
server/sonar-web/src/main/js/apps/users/components/PasswordForm.tsx
server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx
server/sonar-web/src/main/js/apps/users/components/UserForm.tsx
server/sonar-web/src/main/js/apps/users/components/UserScmAccountInput.tsx
server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/TokensForm-test.tsx.snap
server/sonar-web/src/main/js/apps/webhooks/components/CreateWebhookForm.tsx
server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/CreateWebhookForm-test.tsx.snap
server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlay-test.tsx.snap
server/sonar-web/src/main/js/components/common/MarkdownTips.tsx
server/sonar-web/src/main/js/components/controls/Checkbox.tsx
server/sonar-web/src/main/js/components/controls/ConfirmModal.tsx
server/sonar-web/src/main/js/components/controls/Modal.tsx
server/sonar-web/src/main/js/components/controls/Radio.tsx
server/sonar-web/src/main/js/components/controls/SearchSelect.tsx
server/sonar-web/src/main/js/components/controls/ValidationModal.tsx
server/sonar-web/src/main/js/components/controls/__tests__/Checkbox-test.tsx
server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Checkbox-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Radio-test.tsx.snap
server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/SearchSelect-test.tsx.snap
server/sonar-web/src/main/js/components/icons-components/OnboardingAddMembersIcon.tsx
server/sonar-web/src/main/js/components/ui/NewInfoBox.css [deleted file]
server/sonar-web/src/main/js/components/ui/NewInfoBox.tsx [deleted file]
server/sonar-web/src/main/js/components/ui/__tests__/NewInfoBox-test.tsx [deleted file]
server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/NewInfoBox-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/components/ui/buttons.css
server/sonar-web/src/main/js/components/ui/buttons.tsx
server/sonar-web/src/main/js/helpers/testMocks.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 116b2afca93d246e9e6c6845d3bfc5c4d9e2d43b..775b61b0aa6a964cf0222612e609c78e098f36ab 100644 (file)
@@ -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)
index f2175a39a03f0a8402e3fab81cb7be000f717f86..6691c1c36953e38d7197f4ee4edf9521ac6fc05a 100644 (file)
@@ -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");
   }
 
 }
index cbc48881b23257039058955e4d5eabff3fcde40a..1162ec0ad5a87298d1b64891727c9b9203cfbed0 100644 (file)
@@ -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>
index 370030f2a61b990efe1986973c1512d7c90b42a6..0ded3991928b10945d259630c56aab563a2ad2f1 100644 (file)
@@ -28,9 +28,6 @@
   background-color: #fff;
   opacity: 0;
   transition: all 0.2s ease;
-}
-
-.modal.sonarcloud {
   border-radius: 3px;
 }
 
   opacity: 1;
 }
 
+.modal-small {
+  width: 450px;
+  margin-left: -225px;
+}
+
 .modal-medium {
   width: 830px;
   margin-left: -415px;
   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 {
 }
 
 .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;
 }
 .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,
index 67eefb1de6177e3f62f34e94853a7ab9fa9522b7..af8916a1839f71b8aeea1fb29688c171b249a756 100644 (file)
@@ -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,
index e270290c3a5502837bbed2c53609ff73931ac7cd..3a820fe28940d1479e5459ad767f64b3b36238a1 100644 (file)
@@ -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 {
index 2b056a346b4165c9eaa90f2daf6c4df5f660780c..36046303c59762c0e4ed9371007e9c270b737e09 100644 (file)
@@ -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 {
index 45d8d3e0dc235992fb93ca56c2cb214ca9f426bb..f412c82962d5205d32cec8ca7553d4166e9aa896 100644 (file)
@@ -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;
index fa681a26ec8ae4b061034ff03e92f8bb2bead8cb..66e2aa439860fbe290cb6fc92beac85cc3701a5e 100644 (file)
@@ -87,7 +87,7 @@ export default class Password extends React.Component<Props, State> {
               </Alert>
             ))}
 
-          <div className="modal-field">
+          <div className="form-field">
             <label htmlFor="old_password">
               {translate('my_profile.password.old')}
               <em className="mandatory">*</em>
@@ -101,7 +101,7 @@ export default class Password extends React.Component<Props, State> {
               type="password"
             />
           </div>
-          <div className="modal-field">
+          <div className="form-field">
             <label htmlFor="password">
               {translate('my_profile.password.new')}
               <em className="mandatory">*</em>
@@ -115,7 +115,7 @@ export default class Password extends React.Component<Props, State> {
               type="password"
             />
           </div>
-          <div className="modal-field">
+          <div className="form-field">
             <label htmlFor="password_confirmation">
               {translate('my_profile.password.confirm')}
               <em className="mandatory">*</em>
@@ -129,7 +129,7 @@ export default class Password extends React.Component<Props, State> {
               type="password"
             />
           </div>
-          <div className="modal-field">
+          <div className="form-field">
             <SubmitButton id="change-password">
               {translate('my_profile.password.submit')}
             </SubmitButton>
diff --git a/server/sonar-web/src/main/js/apps/account/components/__tests__/Password-test.tsx b/server/sonar-web/src/main/js/apps/account/components/__tests__/Password-test.tsx
new file mode 100644 (file)
index 0000000..2b5900b
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import Password from '../Password';
+import { mockCurrentUser } from '../../../../helpers/testMocks';
+
+it('renders correctly', () => {
+  expect(shallow(<Password user={mockCurrentUser()} />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/account/components/__tests__/__snapshots__/Password-test.tsx.snap b/server/sonar-web/src/main/js/apps/account/components/__tests__/__snapshots__/Password-test.tsx.snap
new file mode 100644 (file)
index 0000000..e16485c
--- /dev/null
@@ -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>
+`;
index a4fe42e1914bfb5a9dbbccc0f31ba08871b3d780..72c73262ce10dd7e9cb3b5e9f2fec65d5fd1ba2f 100644 (file)
@@ -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')}
index 1d8e2735f254520deedecd9a64f76c71c4feb9d7..9ac1066d96f030827ea37fac5a88672d96dd7cd0 100644 (file)
@@ -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')}
index dc47f7e97c5610fdc268aeeb37aee48d97c2fb99..4515f08bff8dbe4db669046b997480969d6c305c 100644 (file)
@@ -3,8 +3,8 @@
 exports[`renders 1`] = `
 <Modal
   contentLabel="scanner context"
-  large={true}
   onRequestClose={[MockFunction]}
+  size="large"
 >
   <div
     className="modal-head"
index 3c3c6519c93bad9b6b61382520b400711479e7d3..aaf5962266d8b4c4d3112873b31c21b7a0bd2cd2 100644 (file)
@@ -3,8 +3,8 @@
 exports[`renders 1`] = `
 <Modal
   contentLabel="stacktrace"
-  large={true}
   onRequestClose={[MockFunction]}
+  size="large"
 >
   <div
     className="modal-head"
index e9ffd00fa0dd560ae7ef3776afcdf3670e553db0..50b5cce46333815a5a1dfc5f711331bcf40f1ff4 100644 (file)
@@ -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}
index 0a8158a153c2cac02caa1b2d23d6aead54045885..b6bf6c9d4ec857929fbe8e8be1dd248cc5931d57 100644 (file)
  */
 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()
                   )}
index a0d0385a8ecff2802756e6ddca5215042bc8762a..c97036fd75a0c513c0d45d71f563c8a740ee390b 100644 (file)
@@ -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">
index 39c11d5468a3307ebae978a6da6301ce5f089216..966a96ca51c24a9004c61720e1bbf5aeb024b558 100644 (file)
@@ -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>
index c7bc85d7817241f2621a2ce98208cc2bb2064b97..0f22be474126c7dac7836b41bccbbdbf5c2e34a3 100644 (file)
@@ -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>
index 74cfa236e86c3350d07fe0b2a163a5f029313059..29f4de18cba36571d3f6863bdb81052115738e9f 100644 (file)
@@ -280,7 +280,7 @@ export default class RuleDetailsProfiles extends React.PureComponent<Props, Stat
 
           {activations.length > 0 && (
             <table
-              className="coding-rules-detail-quality-profiles width100"
+              className="coding-rules-detail-quality-profiles width-100"
               id="coding-rules-detail-quality-profiles">
               <tbody>{activations.map(this.renderActivation)}</tbody>
             </table>
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/ActivationFormModal-test.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/ActivationFormModal-test.tsx
new file mode 100644 (file)
index 0000000..5b9cd54
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import ActivationFormModal from '../ActivationFormModal';
+import { mockQualityProfile, mockRule } from '../../../../helpers/testMocks';
+
+it('render correctly', () => {
+  expect(
+    shallow(
+      <ActivationFormModal
+        modalHeader="title"
+        onClose={jest.fn()}
+        onDone={jest.fn()}
+        organization="foo"
+        profiles={[mockQualityProfile()]}
+        rule={mockRule()}
+      />
+    )
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/BulkChangeModal-test.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/BulkChangeModal-test.tsx
new file mode 100644 (file)
index 0000000..6174a89
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import BulkChangeModal from '../BulkChangeModal';
+import { mockQualityProfile } from '../../../../helpers/testMocks';
+import { Query } from '../../query';
+
+it('render correctly', () => {
+  expect(
+    shallow(
+      <BulkChangeModal
+        action="activate"
+        languages={{ js: { key: 'js', name: 'JavaScript' } }}
+        onClose={jest.fn()}
+        organization="foo"
+        profile={mockQualityProfile()}
+        query={{ languages: ['js'] } as Query}
+        referencedProfiles={{ foo: mockQualityProfile() }}
+        total={42}
+      />
+    )
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/CustomRuleFormModal-test.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/CustomRuleFormModal-test.tsx
new file mode 100644 (file)
index 0000000..8677a87
--- /dev/null
@@ -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}
+    />
+  );
+}
index 2b220a437538f735191eed33d2ffa4a1ce53bd10..3d7b44dcb09b38ac8122c2e236f398fdeab3445f 100644 (file)
 import * as React from 'react';
 import { shallow } from 'enzyme';
 import RuleListItem from '../RuleListItem';
-import { mockEvent } from '../../../../helpers/testMocks';
-
-const rule: T.Rule = {
-  key: 'foo',
-  lang: 'js',
-  langName: 'JavaScript',
-  name: 'Use foo',
-  severity: 'MAJOR',
-  status: 'READY',
-  sysTags: ['a', 'b'],
-  tags: ['x'],
-  type: 'CODE_SMELL'
-};
+import { mockEvent, mockRule } from '../../../../helpers/testMocks';
 
 it('should render', () => {
   expect(shallowRender()).toMatchSnapshot();
@@ -42,7 +30,7 @@ it('should open rule', () => {
   const onOpen = jest.fn();
   const wrapper = shallowRender({ onOpen });
   wrapper.find('Link').prop<Function>('onClick')(mockEvent({ button: 0 }));
-  expect(onOpen).toBeCalledWith('foo');
+  expect(onOpen).toBeCalledWith('javascript:S1067');
 });
 
 function shallowRender(props?: Partial<RuleListItem['props']>) {
@@ -53,7 +41,7 @@ function shallowRender(props?: Partial<RuleListItem['props']>) {
       onFilterChange={jest.fn()}
       onOpen={jest.fn()}
       organization="org"
-      rule={rule}
+      rule={mockRule()}
       selected={false}
       {...props}
     />
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/ActivationFormModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/ActivationFormModal-test.tsx.snap
new file mode 100644 (file)
index 0000000..2225b88
--- /dev/null
@@ -0,0 +1,101 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`render correctly 1`] = `
+<Modal
+  contentLabel="title"
+  onRequestClose={[MockFunction]}
+  size="small"
+>
+  <form
+    onSubmit={[Function]}
+  >
+    <div
+      className="modal-head"
+    >
+      <h2>
+        title
+      </h2>
+    </div>
+    <div
+      className="modal-body"
+    >
+      <Alert
+        variant="info"
+      >
+        coding_rules.active_in_all_profiles
+      </Alert>
+      <div
+        className="modal-field"
+      >
+        <label>
+          coding_rules.quality_profile
+        </label>
+        <Select
+          className="js-profile"
+          clearable={false}
+          disabled={false}
+          onChange={[Function]}
+          options={Array []}
+          value=""
+        />
+      </div>
+      <div
+        className="modal-field"
+      >
+        <label>
+          severity
+        </label>
+        <Select
+          className="js-severity"
+          clearable={false}
+          disabled={false}
+          onChange={[Function]}
+          optionRenderer={[Function]}
+          options={
+            Array [
+              Object {
+                "label": "severity.BLOCKER",
+                "value": "BLOCKER",
+              },
+              Object {
+                "label": "severity.CRITICAL",
+                "value": "CRITICAL",
+              },
+              Object {
+                "label": "severity.MAJOR",
+                "value": "MAJOR",
+              },
+              Object {
+                "label": "severity.MINOR",
+                "value": "MINOR",
+              },
+              Object {
+                "label": "severity.INFO",
+                "value": "INFO",
+              },
+            ]
+          }
+          searchable={false}
+          value="MAJOR"
+          valueRenderer={[Function]}
+        />
+      </div>
+    </div>
+    <footer
+      className="modal-foot"
+    >
+      <SubmitButton
+        disabled={true}
+      >
+        coding_rules.activate
+      </SubmitButton>
+      <ResetButtonLink
+        disabled={false}
+        onClick={[MockFunction]}
+      >
+        cancel
+      </ResetButtonLink>
+    </footer>
+  </form>
+</Modal>
+`;
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap
new file mode 100644 (file)
index 0000000..fed86bf
--- /dev/null
@@ -0,0 +1,56 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`render correctly 1`] = `
+<Modal
+  contentLabel="coding_rules.activate_in_quality_profile (42 coding_rules._rules)"
+  onRequestClose={[MockFunction]}
+  size="small"
+>
+  <form
+    onSubmit={[Function]}
+  >
+    <header
+      className="modal-head"
+    >
+      <h2>
+        coding_rules.activate_in_quality_profile (42 coding_rules._rules)
+      </h2>
+    </header>
+    <div
+      className="modal-body"
+    >
+      <div
+        className="modal-field"
+      >
+        <h3>
+          <label
+            htmlFor="coding-rules-bulk-change-profile"
+          >
+            coding_rules.activate_in
+          </label>
+        </h3>
+        <span>
+          name
+           â€” 
+          are_you_sure
+        </span>
+      </div>
+    </div>
+    <footer
+      className="modal-foot"
+    >
+      <SubmitButton
+        disabled={false}
+        id="coding-rules-submit-bulk-change"
+      >
+        apply
+      </SubmitButton>
+      <ResetButtonLink
+        onClick={[MockFunction]}
+      >
+        cancel
+      </ResetButtonLink>
+    </footer>
+  </form>
+</Modal>
+`;
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/CustomRuleFormModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/CustomRuleFormModal-test.tsx.snap
new file mode 100644 (file)
index 0000000..71f4299
--- /dev/null
@@ -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>
+`;
index 459a816503739e9d6e679bad5bee2e9b43edb651..e1b94a8164a2690290e1bbef5100d4e6d9cae4fc 100644 (file)
@@ -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",
index ca7a4fdde40bb28babd2933176105195ef477535..8450e0dab9407830311bf338f4813911686f38fe 100644 (file)
   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;
 }
index dfaca7bc0f036af5d62ad16715ee8684e7fd8423..27467f3420f9ea0b40a1de297d008443abdebce3 100644 (file)
@@ -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>
index 3043cc08b5f7a97585751ea8dc9182d33e59a0c7..4d294fc14973e96dfda3541a8b40aa48e2ac991b 100644 (file)
@@ -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"
index bce0bcb2c21797cbba45143ed2411fc6a2845a88..3a40718de567f6ff08595101813c30b4bdd49cd9 100644 (file)
@@ -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>
               }
index 4c086a587790d061274d2ca68f69ebdebdd78d11..33d9fcc8d790552e44c460b52e48fc68877f43b8 100644 (file)
@@ -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))
index 8ac2a9e1a070bdd6374be0542047c507e1ce025d..b3fe16d362c8db071fc16a7465f8979da91665b1 100644 (file)
@@ -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()}
index d16fbc702636a9ccc6b3c0a5723b398cb6dcebab..ca0868ae1051318f43243c1fdeb6c313d03a20da 100644 (file)
@@ -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",
index 8cd6afa0fd36d115141c910aa39672bbe8dd083c..cae179cdd68c00f8cf7de6ca65d00dd6cdb3b5b5 100644 (file)
@@ -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
index 26800ae126a7e8a73b3d20fddc928d25b49ff85d..f95556451f880e4cee6b811471164d15764476d5 100644 (file)
@@ -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;
index 400de6cdf6176c7398ae1ad3d148dc92bce65408..38b90fff706cacb17b561dfd351d68235b713440 100644 (file)
@@ -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;
       });
   };
 }
index 261872e75ae6e40a5a074413566e3d10d92fd51a..053b078916630966282f069ba20a5b276b71aa24 100644 (file)
  * 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">
index 91da25f4c4b31c3e73fd28bad50e34a074a8af07..aa23780221955d45cc5d0231cc99e77e39251760 100644 (file)
@@ -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]}
index ad67fa6bc80aaa2ad47558e9a8caeb104c31a3dc..ffc3d9207343c2a3e3198d05ea8fc69833c25627 100644 (file)
@@ -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">
index 69ff6188f6b949324be0aa460613ce2372935175..692174076ae0d06d159cb9495bdb684676c39277 100644 (file)
@@ -4,6 +4,7 @@ exports[`should render form 1`] = `
 <Modal
   contentLabel="header"
   onRequestClose={[MockFunction]}
+  size="small"
 >
   <form
     onSubmit={[Function]}
index 73e2c0ee5f73ab8ad98fe454fcd5171d581121e7..3f5d72afe0810e5b27f29c00ff8077136e2501d3 100644 (file)
@@ -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">
index 2b0954e09fb59a9ca8d5c9fda188c4284b132f2e..58f9268cf6c635e9cfdf3e94eb6e8e1cf88a0dc2 100644 (file)
@@ -4,6 +4,7 @@ exports[`should render form 1`] = `
 <Modal
   contentLabel="header"
   onRequestClose={[MockFunction]}
+  size="small"
 >
   <form
     onSubmit={[Function]}
index 361fb238886972dc6fac501b962f3e0d0463f46b..5082742f98084b443d0d37c0f43d988b3192bf03 100644 (file)
  * 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>
     );
index e45da7a29a6e2c7c7ba862942db2e41aa57595fd..a15fbc837cbec6bcd55eeb55b44be2aed6edfab2 100644 (file)
@@ -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"
index ff12f912eccd458c17ecb70f36393ccaa1a75d56..9964171042b0818a4d2b13aca04473f3d067f2a0 100644 (file)
   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);
+}
index 8cbfe2f6f203d26c46fa4e94fd7e7fb69bc36221..a60df045b649aaa5366c7160e2539f905da06bbc 100644 (file)
@@ -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}
index 26d23cbb965f543b4c73181c81a72031b6b72265..1bc300691ad5794781a86c9c74f217b15c2b8fa7 100644 (file)
  * 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>
     );
   }
 }
index 437b0ed61b96d6c1acac82368457d52fd6ce4a31..4cc845a58ceaf4d8c0b690ff938cf74e3ef34b4b 100644 (file)
@@ -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}
               />
             ))}
index 11a052e4462f990c6d07052002d8ebc8b7c9b2ec..beb8fe92be5f60a7e2be668688ab4a05fc7891da 100644 (file)
@@ -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',
index 143b7a549b39b3d1a64368af51d88a4bc2d780dc..4859e2f2ee58a0fdb9a0e566ec9bc445e4f7a1ef 100644 (file)
@@ -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>
     );
index 9015c4ba8b2ac0cc9750effd0f7f180bee5d3836..5cf908cce7ccace580fe30a114993d9f6afaca0a 100644 (file)
  * 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);
index da2ea484d97ae9a4b4210f6e0d13fc8086f87970..bd141ec20e39a6e15d8b4ad8c8dd36324312215f 100644 (file)
@@ -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 && (
index 9ebb9f57e219ee0a6737841a523b6c6b6213cb32..9276aa1df567f549426da881043f23035b174c55 100644 (file)
@@ -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>
     );
   }
index a412a1b679fde0f7a03ab0ea9f255cd36dc69fba..30b2f90697462160abb63ebc77a0f0d9c96590e0 100644 (file)
@@ -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();
 });
index 2157332788cd93bbee55f55e844d4c1bd39ac56f..f3746540b7aba3f7ed7824f62d7329d59b0b4121 100644 (file)
 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();
-});
+}
index 13a4ebfae5ea018f6619501432cbb6cbc11afa6e..58d4daaf89ccda53fd9b01b55782f2a1e34cf759 100644 (file)
@@ -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}
+    />
+  );
+}
index bb2a10ad8052220e7599c86439eab7c41c20e470..bc81f0149f4a51c1e67d552cdfeeea2f2add7338 100644 (file)
@@ -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}
     />
   );
index eac606f79c0b1528806de388e73474f4728bf7b2..1b11920966600c08639d94ebf20d1a2f64c54ec3 100644 (file)
@@ -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
index 78dd230299a685f73304e1ea98b3a604eb6bedcb..1218009c845d2e80fa304767fa15bf0029163da7 100644 (file)
@@ -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 () => {
index 895c48986d7cf867297939f7c665e87223904b8d..5eafb110cf2359dd19334069612e1e9ada15c70d 100644 (file)
@@ -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
index 660a194e06c1d264cd3f548b008b6d718ee10f82..8a87fc3aa2b5e5b8cb14dbfa8bd46b02708776d9 100644 (file)
@@ -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>
index 9635b1e2685f2b3423eecb9078527868d08ccd96..a99cabd208d3b4d4b914181000d7e8e4b7f5f975 100644 (file)
@@ -33,7 +33,6 @@ exports[`should render a list of members of an organization 1`] = `
           }
         }
         organizationGroups={Array []}
-        removeMember={[MockFunction]}
         updateMemberGroups={[MockFunction]}
       />
       <MembersListItem
index 1d8ecfb92df1e0124b1e50c8647ed44234659ebe..e9852b6c3c397a9826d3289187dacd9edadc254a 100644 (file)
@@ -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
index b97c6a829594e5dd1af9187c3aa11c0c20c7dc6b..414c9f4f30e77e228e04419f29fe446fa5c09608 100644 (file)
@@ -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"
index 0225976a5a6f300f4b4778b296c64eecaeef56a1..c21bb177f1c43974216000f48a6222666b07608c 100644 (file)
@@ -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>
 `;
index 2d451e1086c66abe7b9786048acb01bfe5e4eccb..9f3f3c11a3c390a76fd9104f036c3ab25a7e36f9 100644 (file)
@@ -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 {
index c7c59107ab34aeb5a9e020c7384c891c0a02b302..1be6de36a4c7a0c0ef332f85e73d1cfb2d0b97e1 100644 (file)
@@ -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>
index 452950f680d1b024071e3f6c3e4a4e132f03c392..4d151adf2ab8291c9c18b8cdc8d7451c1dad8547 100644 (file)
@@ -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>
index 80a428e111c4ed4d3badd0a6dcb3d79bd75576fc..19e438405feb201c2face6431603545c94e1f374 100644 (file)
@@ -19,5 +19,5 @@
  */
 .organization-empty {
   margin: 100px auto 0;
-  width: 800px;
+  width: 472px;
 }
index 4310bb215a9392069cad1ed020daedef62567984..078ba67c231b6d765b38d4baae0c6791e98ba596 100644 (file)
@@ -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>
index 22b55f2ca3e8106fd03ab01deb65995d17ce7ace..e948f1810c5a3c4c916d57ea4ca2bd0022964cd8 100644 (file)
@@ -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>
index d58fec02a6c2d16cad67293e04606e200333c6a8..82a33f255a16a87270546de0afacbe176d774b47 100644 (file)
@@ -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>
index 9b040c2381280a3f3e32e861c4d6524ee1993b77..f3eb1a147d989cf1ad6546decd0533161a6566b7 100644 (file)
 import * as React from 'react';
 import DeferredSpinner from '../../../components/common/DeferredSpinner';
 import SimpleModal from '../../../components/controls/SimpleModal';
-import { translate } from '../../../helpers/l10n';
 import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons';
+import { translate } from '../../../helpers/l10n';
 
 interface Props {
   confirmButtonText: string;
   header: string;
-  permissionTemplate?: { description?: string; name: string; projectKeyPattern?: string };
   onClose: () => void;
   onSubmit: (
     data: { description: string; name: string; projectKeyPattern: string }
   ) => Promise<void>;
+  permissionTemplate?: { description?: string; name: string; projectKeyPattern?: string };
 }
 
 interface State {
@@ -79,7 +79,8 @@ export default class Form extends React.PureComponent<Props, State> {
       <SimpleModal
         header={this.props.header}
         onClose={this.props.onClose}
-        onSubmit={this.handleSubmit}>
+        onSubmit={this.handleSubmit}
+        size="small">
         {({ onCloseClick, onFormSubmit, submitting }) => (
           <form id="permission-template-form" onSubmit={onFormSubmit}>
             <header className="modal-head">
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/Form-test.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/Form-test.tsx
new file mode 100644 (file)
index 0000000..1d8dfd3
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import Form from '../Form';
+
+it('render correctly', () => {
+  expect(
+    shallow(
+      <Form confirmButtonText="confirm" header="title" onClose={jest.fn()} onSubmit={jest.fn()} />
+    )
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/Form-test.tsx.snap b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/Form-test.tsx.snap
new file mode 100644 (file)
index 0000000..81c1df3
--- /dev/null
@@ -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>
+`;
index bd5538c91b08a3bc930ad8db96ed708a39253b4c..d27ac5ad9f8d55b116b6129ce49dbca998e80733 100644 (file)
@@ -98,7 +98,11 @@ export default class ApplyTemplate extends React.PureComponent<Props, State> {
     );
 
     return (
-      <SimpleModal header={header} onClose={this.props.onClose} onSubmit={this.handleSubmit}>
+      <SimpleModal
+        header={header}
+        onClose={this.props.onClose}
+        onSubmit={this.handleSubmit}
+        size="small">
         {({ onCloseClick, onFormSubmit, submitting }) => (
           <form id="project-permissions-apply-template-form" onSubmit={onFormSubmit}>
             <header className="modal-head">
diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/ApplyTemplate-test.tsx b/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/ApplyTemplate-test.tsx
new file mode 100644 (file)
index 0000000..221b9e9
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import ApplyTemplate from '../ApplyTemplate';
+import { waitAndUpdate } from '../../../../../helpers/testUtils';
+
+jest.mock('../../../../../api/permissions', () => ({
+  getPermissionTemplates: jest.fn().mockResolvedValue({
+    permissionTemplates: [
+      {
+        id: 'tmp1',
+        name: 'SonarSource projects',
+        createdAt: '2015-11-27T15:20:32+0100',
+        permissions: [
+          { key: 'admin', usersCount: 0, groupsCount: 3 },
+          { key: 'codeviewer', usersCount: 0, groupsCount: 6 }
+        ]
+      }
+    ],
+    defaultTemplates: [{ templateId: 'tmp1', qualifier: 'TRK' }],
+    permissions: [
+      { key: 'admin', name: 'Administer', description: 'Administer access' },
+      { key: 'codeviewer', name: 'See Source Code', description: 'View code' }
+    ]
+  })
+}));
+
+it('render correctly', async () => {
+  const wrapper = shallow(
+    <ApplyTemplate onClose={jest.fn()} organization="foo" project={{ key: 'foo', name: 'Foo' }} />
+  );
+  expect(wrapper).toMatchSnapshot();
+  await waitAndUpdate(wrapper);
+  expect(wrapper.dive()).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/__snapshots__/ApplyTemplate-test.tsx.snap b/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/__snapshots__/ApplyTemplate-test.tsx.snap
new file mode 100644 (file)
index 0000000..84bd72b
--- /dev/null
@@ -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>
+`;
index 98bf37efb856c34a43f8c092f58e05def3b3dc99..1f5bb6c51bf86a1fea03e8761e16e9926934633a 100644 (file)
@@ -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
index c566bbfc4506bdb5442f708939d00b9437035a59..22680c097adace5eb292720490f146b51caa1371 100644 (file)
@@ -23,8 +23,8 @@ import ConfirmModal from '../../../../components/controls/ConfirmModal';
 
 interface Props {
   changeEvent: (event: string, name: string) => Promise<void>;
-  header: string;
   event: T.AnalysisEvent;
+  header: string;
   onClose: () => void;
 }
 
@@ -54,7 +54,8 @@ export default class ChangeEventForm extends React.PureComponent<Props, State> {
         confirmDisable={!name || name === this.props.event.name}
         header={this.props.header}
         onClose={this.props.onClose}
-        onConfirm={this.handleSubmit}>
+        onConfirm={this.handleSubmit}
+        size="small">
         <div className="modal-field">
           <label>{translate('name')}</label>
           <input autoFocus={true} onChange={this.changeInput} type="text" value={name} />
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/AddEventForm-test.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/AddEventForm-test.tsx
new file mode 100644 (file)
index 0000000..6879127
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import AddEventForm from '../AddEventForm';
+
+it('should render correctly', () => {
+  expect(
+    shallow(
+      <AddEventForm
+        addEvent={jest.fn()}
+        addEventButtonText="add"
+        analysis={{
+          key: '1',
+          date: new Date('2019-01-14T15:44:51.000Z'),
+          events: [{ key: '2', category: 'VERSION', name: '1.0' }],
+          codePeriodVersion: '1.0',
+          manualNewCodePeriodBaseline: false
+        }}
+        onClose={jest.fn()}
+      />
+    )
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/ChangeEventForm-test.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/ChangeEventForm-test.tsx
new file mode 100644 (file)
index 0000000..4c739f4
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import ChangeEventForm from '../ChangeEventForm';
+
+it('should render correctly', () => {
+  expect(
+    shallow(
+      <ChangeEventForm
+        changeEvent={jest.fn()}
+        event={{ category: 'VERSION', key: '1', name: '1.0' }}
+        header="change"
+        onClose={jest.fn()}
+      />
+    )
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/__snapshots__/AddEventForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/__snapshots__/AddEventForm-test.tsx.snap
new file mode 100644 (file)
index 0000000..2331d99
--- /dev/null
@@ -0,0 +1,26 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<ConfirmModal
+  confirmButtonText="save"
+  confirmDisable={true}
+  header="add"
+  onClose={[MockFunction]}
+  onConfirm={[Function]}
+  size="small"
+>
+  <div
+    className="modal-field"
+  >
+    <label>
+      name
+    </label>
+    <input
+      autoFocus={true}
+      onChange={[Function]}
+      type="text"
+      value=""
+    />
+  </div>
+</ConfirmModal>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/__snapshots__/ChangeEventForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/__snapshots__/ChangeEventForm-test.tsx.snap
new file mode 100644 (file)
index 0000000..70debbd
--- /dev/null
@@ -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>
+`;
index 3ce5526a0fb9b9d8bb2c873fe5c5d309f20a1f73..0a5ae1e36f0ad9ee44fcd8bb82b15b97ce5ac5cb 100644 (file)
@@ -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>
index 6a486e563eb5b70509301060a1d92a8e8c5f21fe..6cfbcee0d440b24e3622fc216e1eb62b5670356a 100644 (file)
@@ -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>
               )}
index 77c4b64b7f165ba35487859a425cdf5e465b96bc..fc11d1199160d27ba452cd18d04c2934b8babb21 100644 (file)
@@ -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"
index 1c038c720ff209c3e2a4a5c30d5c9814a5ebb668..45cb96b4022af75003934ddbfb17021223250883 100644 (file)
@@ -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>
index 4b6285a29d2c5b6d8f252e781001c2e8343034b6..5ad2acbd937469831dad0cfdc09155c5683cfc8a 100644 (file)
@@ -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">
index b56b44cef77fdd04a40ca935dc69587fb9003d51..b46aff716c6466f3b98deb24f373e16e7d3ea14f 100644 (file)
@@ -4,6 +4,7 @@ exports[`should create link 1`] = `
 <Modal
   contentLabel="project_links.create_new_project_link"
   onRequestClose={[MockFunction]}
+  size="small"
 >
   <form
     onSubmit={[Function]}
index 63d8ae46b7f9195b6e711b80a72b1c22c0d40dfa..86e8b4e1dce6d12af621dc331e94359eac84344c 100644 (file)
@@ -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>
index dd78ed4eb3c8688a04c50d1dd229ac24c106f3f2..a916e81d16e85e0a8060bdda79fd93c7afa3a7c9 100644 (file)
@@ -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}
index 3582abeb9cfc4dd79c264677afa88522f351fed7..9b7ddf1b306a753ec2d08a5b19d12a6859bd5eb9 100644 (file)
@@ -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"
index 859052104d310896e147878dadb537c9f6e558b9..9879d2fd163cef4fbee03e39b521b6aab7f51e66 100644 (file)
@@ -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={
index 7284f830995b5bb2b09cd626af94ad6a35fbd76f..92f9219f92c71623cd89c32af3ddd87338a79dca 100644 (file)
@@ -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>
index de16855c471aeae36b1cf05fd977b125bd1cb6fd..b2cb17a398ee71fbe32e7cf6568b06c8893c6e8d 100644 (file)
@@ -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>
+      );
     }
   }
 }
index e6fc44670df00795dcc1ec6608b46472d614bfe2..a631c273c0516308b2d34070941d2c868784644f 100644 (file)
@@ -71,7 +71,8 @@ class CopyQualityGateForm extends React.PureComponent<Props, State> {
         confirmDisable={confirmDisable}
         header={translate('quality_gates.copy')}
         onClose={this.props.onClose}
-        onConfirm={this.handleCopy}>
+        onConfirm={this.handleCopy}
+        size="small">
         <div className="modal-field">
           <label htmlFor="quality-gate-form-name">
             {translate('name')}
index a2ffb8d04b36e5bb424e82fc17055b3a10bb11ee..c310bb32ab7c15173384dec5e3946b6a084ede8e 100644 (file)
@@ -67,7 +67,8 @@ class CreateQualityGateForm extends React.PureComponent<Props, State> {
         confirmDisable={!name}
         header={translate('quality_gates.create')}
         onClose={this.props.onClose}
-        onConfirm={this.handleCreate}>
+        onConfirm={this.handleCreate}
+        size="small">
         <div className="modal-field">
           <label htmlFor="quality-gate-form-name">
             {translate('name')}
index 17c3da58fcb67adb11ea2b720adb046c84d27629..86bba91482591c84baf93d70b8fbe15b0405c5c4 100644 (file)
@@ -41,7 +41,8 @@ interface Option {
 export default class MetricSelect extends React.PureComponent<Props, State> {
   state = { value: -1 };
 
-  handleChange = ({ value }: Option) => {
+  handleChange = (option: Option | null) => {
+    const value = option ? option.value : -1;
     this.setState({ value });
     this.props.onMetricChange(this.props.metrics[value]);
   };
@@ -74,7 +75,7 @@ export default class MetricSelect extends React.PureComponent<Props, State> {
 
     return (
       <Select
-        className="text-middle input-large"
+        className="text-middle"
         id="condition-metric"
         onChange={this.handleChange}
         options={optionsWithDomains}
index b468b58d6140e50f2e95cf869030bdd8f4e3a376..30bc667f7b7440113296de3c05e53b85a2899c26 100644 (file)
@@ -67,7 +67,8 @@ export default class RenameQualityGateForm extends React.PureComponent<Props, St
         confirmDisable={confirmDisable}
         header={translate('quality_gates.rename')}
         onClose={this.props.onClose}
-        onConfirm={this.handleRename}>
+        onConfirm={this.handleRename}
+        size="small">
         <div className="modal-field">
           <label htmlFor="quality-gate-form-name">
             {translate('name')}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionModal-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionModal-test.tsx
new file mode 100644 (file)
index 0000000..b1769f7
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import ConditionModal from '../ConditionModal';
+import { mockQualityGate } from '../../../../helpers/testMocks';
+
+it('should render correctly', () => {
+  const wrapper = shallowRender();
+  expect(wrapper).toMatchSnapshot();
+
+  wrapper.instance().handleMetricChange({ id: '1', key: 'foo', name: 'Foo', type: 'PERCENT' });
+  expect(wrapper).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<ConditionModal['props']> = {}) {
+  return shallow<ConditionModal>(
+    <ConditionModal
+      header="a"
+      onAddCondition={jest.fn()}
+      onClose={jest.fn()}
+      qualityGate={mockQualityGate()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionOperator-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionOperator-test.tsx
new file mode 100644 (file)
index 0000000..ef6c9fd
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import ConditionOperator from '../ConditionOperator';
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<ConditionOperator['props']> = {}) {
+  return shallow(
+    <ConditionOperator
+      metric={{ id: '1', key: 'foo', name: 'Foo', type: 'PERCENT' }}
+      onOperatorChange={jest.fn()}
+      op="LT"
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/CopyQualityGateForm-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/CopyQualityGateForm-test.tsx
new file mode 100644 (file)
index 0000000..85310ab
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * 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 CopyQualityGateForm from '../CopyQualityGateForm';
+import { mockQualityGate } from '../../../../helpers/testMocks';
+
+it('should render correctly', () => {
+  expect(
+    shallow(
+      <CopyQualityGateForm onClose={jest.fn()} onCopy={jest.fn()} qualityGate={mockQualityGate()} />
+    )
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/CreateQualityGateForm-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/CreateQualityGateForm-test.tsx
new file mode 100644 (file)
index 0000000..dde2faf
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import CreateQualityGateForm from '../CreateQualityGateForm';
+
+it('should render correctly', () => {
+  expect(
+    shallow(<CreateQualityGateForm onClose={jest.fn()} onCreate={jest.fn()} />)
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/MetricSelect-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/MetricSelect-test.tsx
new file mode 100644 (file)
index 0000000..ad2d9fe
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import MetricSelect from '../MetricSelect';
+
+it('should render correctly', () => {
+  expect(
+    shallow(
+      <MetricSelect
+        metrics={[
+          {
+            id: '1',
+            key: '1',
+            name: 'metric 1',
+            type: 'test'
+          }
+        ]}
+        onMetricChange={jest.fn()}
+      />
+    )
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/RenameQualityGateForm-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/RenameQualityGateForm-test.tsx
new file mode 100644 (file)
index 0000000..1ed1d7d
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import RenameQualityGateForm from '../RenameQualityGateForm';
+import { mockQualityGate } from '../../../../helpers/testMocks';
+
+it('should render correctly', () => {
+  expect(
+    shallow(
+      <RenameQualityGateForm
+        onClose={jest.fn()}
+        onRename={jest.fn()}
+        qualityGate={mockQualityGate()}
+      />
+    )
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionModal-test.tsx.snap
new file mode 100644 (file)
index 0000000..ab32f89
--- /dev/null
@@ -0,0 +1,85 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<ConfirmModal
+  confirmButtonText="a"
+  confirmDisable={true}
+  header="a"
+  onClose={[MockFunction]}
+  onConfirm={[Function]}
+  size="small"
+>
+  <div
+    className="modal-field"
+  >
+    <label
+      htmlFor="condition-metric"
+    >
+      quality_gates.conditions.metric
+    </label>
+  </div>
+</ConfirmModal>
+`;
+
+exports[`should render correctly 2`] = `
+<ConfirmModal
+  confirmButtonText="a"
+  confirmDisable={false}
+  header="a"
+  onClose={[MockFunction]}
+  onConfirm={[Function]}
+  size="small"
+>
+  <div
+    className="modal-field"
+  >
+    <label
+      htmlFor="condition-metric"
+    >
+      quality_gates.conditions.metric
+    </label>
+  </div>
+  <div
+    className="modal-field display-inline-block"
+  >
+    <label
+      htmlFor="condition-operator"
+    >
+      quality_gates.conditions.operator
+    </label>
+    <ConditionOperator
+      metric={
+        Object {
+          "id": "1",
+          "key": "foo",
+          "name": "Foo",
+          "type": "PERCENT",
+        }
+      }
+      onOperatorChange={[Function]}
+    />
+  </div>
+  <div
+    className="modal-field display-inline-block spacer-left"
+  >
+    <label
+      htmlFor="condition-threshold"
+    >
+      quality_gates.conditions.error
+    </label>
+    <ThresholdInput
+      metric={
+        Object {
+          "id": "1",
+          "key": "foo",
+          "name": "Foo",
+          "type": "PERCENT",
+        }
+      }
+      name="error"
+      onChange={[Function]}
+      value=""
+    />
+  </div>
+</ConfirmModal>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionOperator-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionOperator-test.tsx.snap
new file mode 100644 (file)
index 0000000..a229502
--- /dev/null
@@ -0,0 +1,26 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Select
+  autoFocus={true}
+  className="input-medium"
+  clearable={false}
+  id="condition-operator"
+  name="operator"
+  onChange={[Function]}
+  options={
+    Array [
+      Object {
+        "label": "quality_gates.operator.LT",
+        "value": "LT",
+      },
+      Object {
+        "label": "quality_gates.operator.GT",
+        "value": "GT",
+      },
+    ]
+  }
+  searchable={false}
+  value="LT"
+/>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/CopyQualityGateForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/CopyQualityGateForm-test.tsx.snap
new file mode 100644 (file)
index 0000000..dae3160
--- /dev/null
@@ -0,0 +1,14 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<CopyQualityGateForm
+  onClose={[MockFunction]}
+  onCopy={[MockFunction]}
+  qualityGate={
+    Object {
+      "id": 1,
+      "name": "qualitygate",
+    }
+  }
+/>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/CreateQualityGateForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/CreateQualityGateForm-test.tsx.snap
new file mode 100644 (file)
index 0000000..d770245
--- /dev/null
@@ -0,0 +1,8 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<CreateQualityGateForm
+  onClose={[MockFunction]}
+  onCreate={[MockFunction]}
+/>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/MetricSelect-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/MetricSelect-test.tsx.snap
new file mode 100644 (file)
index 0000000..b03dceb
--- /dev/null
@@ -0,0 +1,20 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Select
+  className="text-middle"
+  id="condition-metric"
+  onChange={[Function]}
+  options={
+    Array [
+      Object {
+        "domain": undefined,
+        "label": "metric 1",
+        "value": 0,
+      },
+    ]
+  }
+  placeholder="search.search_for_metrics"
+  value={-1}
+/>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/RenameQualityGateForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/RenameQualityGateForm-test.tsx.snap
new file mode 100644 (file)
index 0000000..3a73938
--- /dev/null
@@ -0,0 +1,37 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<ConfirmModal
+  confirmButtonText="rename"
+  confirmDisable={true}
+  header="quality_gates.rename"
+  onClose={[MockFunction]}
+  onConfirm={[Function]}
+  size="small"
+>
+  <div
+    className="modal-field"
+  >
+    <label
+      htmlFor="quality-gate-form-name"
+    >
+      name
+      <em
+        className="mandatory"
+      >
+        *
+      </em>
+    </label>
+    <input
+      autoFocus={true}
+      id="quality-gate-form-name"
+      maxLength={100}
+      onChange={[Function]}
+      required={true}
+      size={50}
+      type="text"
+      value="qualitygate"
+    />
+  </div>
+</ConfirmModal>
+`;
index 64ac78e752c5e15d87bec11910becf79a8231e48..bcd4405a9c2e62debc46f5a34086a96aa047949e 100644 (file)
@@ -80,7 +80,7 @@ export default class CopyProfileForm extends React.PureComponent<Props, State> {
       this.state.loading || !this.state.name || this.state.name === profile.name;
 
     return (
-      <Modal contentLabel={header} onRequestClose={this.props.onClose}>
+      <Modal contentLabel={header} onRequestClose={this.props.onClose} size="small">
         <form id="copy-profile-form" onSubmit={this.handleFormSubmit}>
           <div className="modal-head">
             <h2>{header}</h2>
index 6b8fd8db20d27de05ef4dae218a43861af0947bf..a686ceb5e185a4ace8d3a1000a5cdc90eeea7c82 100644 (file)
@@ -96,7 +96,7 @@ export default class ExtendProfileForm extends React.PureComponent<Props, State>
     );
 
     return (
-      <Modal contentLabel={header} onRequestClose={this.props.onClose}>
+      <Modal contentLabel={header} onRequestClose={this.props.onClose} size="small">
         <form>
           <div className="modal-head">
             <h2>{header}</h2>
index 1f3ebc9114ba00a78782cbf3d3def797cde6461e..714dda6cce871258cb9d61ccdeed656c287ec5ae 100644 (file)
@@ -80,7 +80,7 @@ export default class RenameProfileForm extends React.PureComponent<Props, State>
       this.state.loading || !this.state.name || this.state.name === profile.name;
 
     return (
-      <Modal contentLabel={header} onRequestClose={this.props.onClose}>
+      <Modal contentLabel={header} onRequestClose={this.props.onClose} size="small">
         <form id="rename-profile-form" onSubmit={this.handleFormSubmit}>
           <div className="modal-head">
             <h2>{header}</h2>
index f0dbf25d18ce46a4b406c2560f3910e15dd8fefb..d055f1c896895f9b9bc4f607e9699832f626b367 100644 (file)
@@ -4,6 +4,7 @@ exports[`should render correctly 1`] = `
 <Modal
   contentLabel="quality_profiles.extend_x_title.name.JavaScript"
   onRequestClose={[MockFunction]}
+  size="small"
 >
   <form>
     <div
index e44df26c5b517a995c2c4f830d31e44d3a0ba398..1795c8e7045288217410aef9c8a56f89297ccc59 100644 (file)
@@ -95,7 +95,8 @@ export default class ChangeParentForm extends React.PureComponent<Props, State>
     return (
       <Modal
         contentLabel={translate('quality_profiles.change_parent')}
-        onRequestClose={this.props.onClose}>
+        onRequestClose={this.props.onClose}
+        size="small">
         <form id="change-profile-parent-form" onSubmit={this.handleFormSubmit}>
           <div className="modal-head">
             <h2>{translate('quality_profiles.change_parent')}</h2>
index 35539ffc3685d198c92f6dee835b1b9cdfa71d29..2cb0872c786743fec6670662f0efe19f429531e3 100644 (file)
@@ -119,7 +119,7 @@ export default class ProfilePermissionsForm extends React.PureComponent<Props, S
         </header>
         <form onSubmit={this.handleFormSubmit}>
           <div className="modal-body">
-            <div className="modal-large-field">
+            <div className="modal-field">
               <label>{translate('quality_profiles.search_description')}</label>
               <ProfilePermissionsFormSelect
                 onChange={this.handleValueChange}
index acf1d98288dd865ac6004fb2bfd073715d3d2d55..9cf5a9df4b277f317961e2a91e5cfb0a304fd075 100644 (file)
@@ -122,15 +122,15 @@ function getStringValue(option: Option) {
 
 function optionRenderer(option: OptionWithValue) {
   return isUser(option) ? (
-    <div>
+    <>
       <Avatar hash={option.avatar} name={option.name} size={16} />
       <strong className="spacer-left">{option.name}</strong>
       <span className="note little-spacer-left">{option.login}</span>
-    </div>
+    </>
   ) : (
-    <div>
+    <>
       <GroupIcon size={16} />
       <strong className="spacer-left">{option.name}</strong>
-    </div>
+    </>
   );
 }
index 4caa41a48c1a54fe6c48797a192d044d8bbd3a7a..8d1e901d79a683aa8a567cf11ccea6d9f96daf0d 100644 (file)
@@ -19,7 +19,7 @@ exports[`adds group 1`] = `
       className="modal-body"
     >
       <div
-        className="modal-large-field"
+        className="modal-field"
       >
         <label>
           quality_profiles.search_description
@@ -67,7 +67,7 @@ exports[`adds group 2`] = `
       className="modal-body"
     >
       <div
-        className="modal-large-field"
+        className="modal-field"
       >
         <label>
           quality_profiles.search_description
@@ -120,7 +120,7 @@ exports[`adds group 3`] = `
       className="modal-body"
     >
       <div
-        className="modal-large-field"
+        className="modal-field"
       >
         <label>
           quality_profiles.search_description
@@ -176,7 +176,7 @@ exports[`adds user 1`] = `
       className="modal-body"
     >
       <div
-        className="modal-large-field"
+        className="modal-field"
       >
         <label>
           quality_profiles.search_description
@@ -224,7 +224,7 @@ exports[`adds user 2`] = `
       className="modal-body"
     >
       <div
-        className="modal-large-field"
+        className="modal-field"
       >
         <label>
           quality_profiles.search_description
@@ -277,7 +277,7 @@ exports[`adds user 3`] = `
       className="modal-body"
     >
       <div
-        className="modal-large-field"
+        className="modal-field"
       >
         <label>
           quality_profiles.search_description
index 876f85a88a506cffae0b401ab96146181ecbe0ba..7b19310ca2fb2eb8712b8ddb7fcae116710612fc 100644 (file)
@@ -134,7 +134,7 @@ export default class CreateProfileForm extends React.PureComponent<Props, State>
     }
 
     return (
-      <Modal contentLabel={header} onRequestClose={this.props.onClose}>
+      <Modal contentLabel={header} onRequestClose={this.props.onClose} size="small">
         <form id="create-profile-form" onSubmit={this.handleFormSubmit}>
           <div className="modal-head">
             <h2>{header}</h2>
@@ -163,7 +163,7 @@ export default class CreateProfileForm extends React.PureComponent<Props, State>
                   value={this.state.name}
                 />
               </div>
-              <div className="modal-field spacer-bottom">
+              <div className="modal-field">
                 <label htmlFor="create-profile-language">
                   {translate('language')}
                   <em className="mandatory">*</em>
index 51dcf1783f7a974aea93fd3851ad2e8c182ef5c9..d8462260ce22c9b55df1b8e28ae4be5701469b44 100644 (file)
@@ -79,13 +79,34 @@ export default class RestoreProfileForm extends React.PureComponent<Props, State
     );
   };
 
+  renderAlert(profile: { name: string }, ruleFailures = 0, ruleSuccesses: number): React.ReactNode {
+    return ruleFailures ? (
+      <Alert variant="warning">
+        {translateWithParameters(
+          'quality_profiles.restore_profile.warning',
+          profile.name,
+          ruleSuccesses,
+          ruleFailures
+        )}
+      </Alert>
+    ) : (
+      <Alert variant="success">
+        {translateWithParameters(
+          'quality_profiles.restore_profile.success',
+          profile.name,
+          ruleSuccesses
+        )}
+      </Alert>
+    );
+  }
+
   render() {
     const header = translate('quality_profiles.restore_profile');
 
     const { loading, profile, ruleFailures, ruleSuccesses } = this.state;
 
     return (
-      <Modal contentLabel={header} onRequestClose={this.props.onClose}>
+      <Modal contentLabel={header} onRequestClose={this.props.onClose} size="small">
         <form id="restore-profile-form" onSubmit={this.handleFormSubmit}>
           <div className="modal-head">
             <h2>{header}</h2>
@@ -93,24 +114,7 @@ export default class RestoreProfileForm extends React.PureComponent<Props, State
 
           <div className="modal-body">
             {profile != null && ruleSuccesses != null ? (
-              ruleFailures ? (
-                <Alert variant="warning">
-                  {translateWithParameters(
-                    'quality_profiles.restore_profile.warning',
-                    profile.name,
-                    ruleSuccesses,
-                    ruleFailures
-                  )}
-                </Alert>
-              ) : (
-                <Alert variant="success">
-                  {translateWithParameters(
-                    'quality_profiles.restore_profile.success',
-                    profile.name,
-                    ruleSuccesses
-                  )}
-                </Alert>
-              )
+              this.renderAlert(profile, ruleFailures, ruleSuccesses)
             ) : (
               <div className="modal-field">
                 <label htmlFor="restore-profile-backup">
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/CreateProfileForm-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/CreateProfileForm-test.tsx
new file mode 100644 (file)
index 0000000..6d5c388
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import CreateProfileForm from '../CreateProfileForm';
+import { mockQualityProfile } from '../../../../helpers/testMocks';
+
+jest.mock('../../../../api/quality-profiles', () => ({
+  changeProfileParent: jest.fn(),
+  createQualityProfile: jest.fn(),
+  getImporters: jest.fn().mockResolvedValue([])
+}));
+
+it('should render correctly', () => {
+  expect(
+    shallow(
+      <CreateProfileForm
+        languages={[{ key: 'kr', name: 'Hangeul' }]}
+        onClose={jest.fn()}
+        onCreate={jest.fn()}
+        organization="org"
+        profiles={[mockQualityProfile()]}
+      />
+    )
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/RestoreProfileForm-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/RestoreProfileForm-test.tsx
new file mode 100644 (file)
index 0000000..f5c082b
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import RestoreProfileForm from '../RestoreProfileForm';
+
+it('should render correctly', () => {
+  expect(
+    shallow(<RestoreProfileForm onClose={jest.fn()} onRestore={jest.fn()} organization="org" />)
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/CreateProfileForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/CreateProfileForm-test.tsx.snap
new file mode 100644 (file)
index 0000000..e4c75d3
--- /dev/null
@@ -0,0 +1,39 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Modal
+  contentLabel="quality_profiles.new_profile"
+  onRequestClose={[MockFunction]}
+  size="small"
+>
+  <form
+    id="create-profile-form"
+    onSubmit={[Function]}
+  >
+    <div
+      className="modal-head"
+    >
+      <h2>
+        quality_profiles.new_profile
+      </h2>
+    </div>
+    <div
+      className="modal-body"
+    >
+      <i
+        className="spinner"
+      />
+    </div>
+    <div
+      className="modal-foot"
+    >
+      <ResetButtonLink
+        id="create-profile-cancel"
+        onClick={[MockFunction]}
+      >
+        cancel
+      </ResetButtonLink>
+    </div>
+  </form>
+</Modal>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/RestoreProfileForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/RestoreProfileForm-test.tsx.snap
new file mode 100644 (file)
index 0000000..5656667
--- /dev/null
@@ -0,0 +1,62 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Modal
+  contentLabel="quality_profiles.restore_profile"
+  onRequestClose={[MockFunction]}
+  size="small"
+>
+  <form
+    id="restore-profile-form"
+    onSubmit={[Function]}
+  >
+    <div
+      className="modal-head"
+    >
+      <h2>
+        quality_profiles.restore_profile
+      </h2>
+    </div>
+    <div
+      className="modal-body"
+    >
+      <div
+        className="modal-field"
+      >
+        <label
+          htmlFor="restore-profile-backup"
+        >
+          backup
+          <em
+            className="mandatory"
+          >
+            *
+          </em>
+        </label>
+        <input
+          id="restore-profile-backup"
+          name="backup"
+          required={true}
+          type="file"
+        />
+      </div>
+    </div>
+    <div
+      className="modal-foot"
+    >
+      <SubmitButton
+        disabled={false}
+        id="restore-profile-submit"
+      >
+        restore
+      </SubmitButton>
+      <ResetButtonLink
+        id="restore-profile-cancel"
+        onClick={[MockFunction]}
+      >
+        cancel
+      </ResetButtonLink>
+    </div>
+  </form>
+</Modal>
+`;
index 19ad53f17b2cb96aaa8e1637391384ca81c34a64..278937d62d4ee300a3aad1d4d5e174eb3d26304f 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { sendTestEmail } from '../../../api/settings';
-import { parseError } from '../../../helpers/request';
-import { SubmitButton } from '../../../components/ui/buttons';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
 import { Alert } from '../../../components/ui/Alert';
+import { SubmitButton } from '../../../components/ui/buttons';
 import { withCurrentUser } from '../../../components/hoc/withCurrentUser';
+import { sendTestEmail } from '../../../api/settings';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { parseError } from '../../../helpers/request';
 
 interface Props {
   currentUser: T.LoggedInUser;
@@ -38,7 +39,7 @@ interface State {
   error?: string;
 }
 
-class EmailForm extends React.PureComponent<Props, State> {
+export class EmailForm extends React.PureComponent<Props, State> {
   mounted = false;
 
   constructor(props: Props) {
@@ -93,12 +94,16 @@ class EmailForm extends React.PureComponent<Props, State> {
 
   render() {
     return (
-      <div className="huge-spacer-top">
-        <h3 className="spacer-bottom">{translate('email_configuration.test.title')}</h3>
-
-        <form onSubmit={this.handleFormSubmit} style={{ marginLeft: 201 }}>
+      <div className="settings-definition">
+        <div className="settings-definition-left">
+          <h3 className="settings-definition-name">
+            {translate('email_configuration.test.title')}
+          </h3>
+        </div>
+
+        <form className="settings-definition-right" onSubmit={this.handleFormSubmit}>
           {this.state.success && (
-            <div className="modal-field">
+            <div className="form-field">
               <Alert variant="success">
                 {translateWithParameters(
                   'email_configuration.test.email_was_sent_to_x',
@@ -109,12 +114,12 @@ class EmailForm extends React.PureComponent<Props, State> {
           )}
 
           {this.state.error != null && (
-            <div className="modal-field">
+            <div className="form-field">
               <Alert variant="error">{this.state.error}</Alert>
             </div>
           )}
 
-          <div className="modal-field">
+          <div className="form-field">
             <label htmlFor="test-email-to">
               {translate('email_configuration.test.to_address')}
               <em className="mandatory">*</em>
@@ -129,7 +134,7 @@ class EmailForm extends React.PureComponent<Props, State> {
               value={this.state.recipient}
             />
           </div>
-          <div className="modal-field">
+          <div className="form-field">
             <label htmlFor="test-email-subject">
               {translate('email_configuration.test.subject')}
             </label>
@@ -142,7 +147,7 @@ class EmailForm extends React.PureComponent<Props, State> {
               value={this.state.subject}
             />
           </div>
-          <div className="modal-field">
+          <div className="form-field">
             <label htmlFor="test-email-message">
               {translate('email_configuration.test.message')}
               <em className="mandatory">*</em>
@@ -158,12 +163,10 @@ class EmailForm extends React.PureComponent<Props, State> {
             />
           </div>
 
-          <div className="modal-field">
-            {this.state.loading && <i className="spacer-right spinner" />}
-            <SubmitButton disabled={this.state.loading}>
-              {translate('email_configuration.test.send')}
-            </SubmitButton>
-          </div>
+          <SubmitButton disabled={this.state.loading}>
+            {translate('email_configuration.test.send')}
+          </SubmitButton>
+          {this.state.loading && <DeferredSpinner className="spacer-left" />}
         </form>
       </div>
     );
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/EmailForm-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/EmailForm-test.tsx
new file mode 100644 (file)
index 0000000..6c714a8
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import { EmailForm } from '../EmailForm';
+import { mockCurrentUser } from '../../../../helpers/testMocks';
+
+it('should render correctly', () => {
+  expect(shallow(<EmailForm currentUser={mockCurrentUser()} />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/EmailForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/EmailForm-test.tsx.snap
new file mode 100644 (file)
index 0000000..cc15cb3
--- /dev/null
@@ -0,0 +1,90 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+  className="settings-definition"
+>
+  <div
+    className="settings-definition-left"
+  >
+    <h3
+      className="settings-definition-name"
+    >
+      email_configuration.test.title
+    </h3>
+  </div>
+  <form
+    className="settings-definition-right"
+    onSubmit={[Function]}
+  >
+    <div
+      className="form-field"
+    >
+      <label
+        htmlFor="test-email-to"
+      >
+        email_configuration.test.to_address
+        <em
+          className="mandatory"
+        >
+          *
+        </em>
+      </label>
+      <input
+        className="settings-large-input"
+        disabled={false}
+        id="test-email-to"
+        onChange={[Function]}
+        required={true}
+        type="email"
+        value=""
+      />
+    </div>
+    <div
+      className="form-field"
+    >
+      <label
+        htmlFor="test-email-subject"
+      >
+        email_configuration.test.subject
+      </label>
+      <input
+        className="settings-large-input"
+        disabled={false}
+        id="test-email-subject"
+        onChange={[Function]}
+        type="text"
+        value="email_configuration.test.subject"
+      />
+    </div>
+    <div
+      className="form-field"
+    >
+      <label
+        htmlFor="test-email-message"
+      >
+        email_configuration.test.message
+        <em
+          className="mandatory"
+        >
+          *
+        </em>
+      </label>
+      <textarea
+        className="settings-large-input"
+        disabled={false}
+        id="test-email-message"
+        onChange={[Function]}
+        required={true}
+        rows={5}
+        value="email_configuration.test.message_text"
+      />
+    </div>
+    <SubmitButton
+      disabled={false}
+    >
+      email_configuration.test.send
+    </SubmitButton>
+  </form>
+</div>
+`;
index 57254c2fefa5c9403f9bd28dae472b96a3c07206..8cfd77b3938add0a9fa6f4b23a6c31afed9a4133 100644 (file)
@@ -89,11 +89,11 @@ export default class EncryptionForm extends React.PureComponent<Props, State> {
         <form className="big-spacer-bottom" id="encryption-form" onSubmit={this.handleEncrypt}>
           <textarea
             autoFocus={true}
-            className="input-super-large"
+            className="abs-width-600"
             id="encryption-form-value"
             onChange={this.handleChange}
             required={true}
-            rows={3}
+            rows={5}
             value={this.state.value}
           />
           <div className="spacer-top">
index 1163399e78bb6e7072da7b66915b2f759deffb09..3051b630d83384f5c27d4b0db77ecf8560c08871 100644 (file)
@@ -32,18 +32,11 @@ export interface Props {
   currentUser: T.LoggedInUser;
   onClose: () => void;
   onOpenProjectOnboarding: () => void;
-  skipOnboarding: () => void;
   userOrganizations: T.Organization[];
 }
 
 export function OnboardingModal(props: Props) {
-  const {
-    currentUser,
-    onClose,
-    onOpenProjectOnboarding,
-    skipOnboarding,
-    userOrganizations
-  } = props;
+  const { currentUser, onClose, onOpenProjectOnboarding, userOrganizations } = props;
 
   const organizations = userOrganizations.filter(o => o.key !== currentUser.personalOrganization);
 
@@ -51,9 +44,9 @@ export function OnboardingModal(props: Props) {
   return (
     <Modal
       contentLabel={header}
-      medium={true}
       onRequestClose={onClose}
-      shouldCloseOnOverlayClick={false}>
+      shouldCloseOnOverlayClick={false}
+      size={organizations.length > 0 ? 'medium' : 'small'}>
       <div className="modal-head">
         <h2>{translate('onboarding.header')}</h2>
         <p className="spacer-top">{translate('onboarding.header.description')}</p>
@@ -61,9 +54,7 @@ export function OnboardingModal(props: Props) {
       <div className="modal-body text-center display-flex-row huge-spacer-top huge-spacer-bottom">
         <div className="flex-1">
           <OnboardingProjectIcon className="big-spacer-bottom" />
-          <h6 className="onboarding-choice-name big-spacer-bottom">
-            {translate('onboarding.analyze_your_code')}
-          </h6>
+          <h3 className="big-spacer-bottom">{translate('onboarding.analyze_your_code')}</h3>
           <Button onClick={onOpenProjectOnboarding}>
             {translate('onboarding.project.create')}
           </Button>
@@ -75,13 +66,10 @@ export function OnboardingModal(props: Props) {
             </div>
             <div className="flex-1">
               <OnboardingTeamIcon className="big-spacer-bottom" />
-              <h6 className="onboarding-choice-name big-spacer-bottom">
+              <h3 className="big-spacer-bottom">
                 {translate('onboarding.browse_your_organizations')}
-              </h6>
-              <OrganizationsShortList
-                organizations={organizations}
-                skipOnboarding={skipOnboarding}
-              />
+              </h3>
+              <OrganizationsShortList onClick={onClose} organizations={organizations} />
             </div>
           </>
         )}
index 5bda42d861b990984c2bd7d92498f096b93c6cbb..52644d6b02e1f69cfa4c7501b35e5f143dd58405 100644 (file)
@@ -42,7 +42,6 @@ export class OnboardingPage extends React.PureComponent<Props> {
           <OnboardingModal
             onClose={this.closeOnboarding}
             onOpenProjectOnboarding={openProjectOnboarding}
-            skipOnboarding={this.props.skipOnboarding}
           />
         )}
       </OnboardingContext.Consumer>
index a7eecc85f6fb4ddeb04de57fa5e1b9614960b843..d7685c0cffb0ce2a930ed9129f938baba66dbbf3 100644 (file)
@@ -25,10 +25,10 @@ import { translate, translateWithParameters } from '../../../helpers/l10n';
 
 export interface Props {
   organizations: T.Organization[];
-  skipOnboarding: () => void;
+  onClick: () => void;
 }
 
-export default function OrganizationsShortList({ organizations, skipOnboarding }: Props) {
+export default function OrganizationsShortList({ onClick, organizations }: Props) {
   if (organizations.length === 0) {
     return null;
   }
@@ -39,26 +39,27 @@ export default function OrganizationsShortList({ organizations, skipOnboarding }
 
   return (
     <div>
-      <ul className="account-projects-list">
+      <ul>
         {organizationsShown.map(organization => (
           <li key={organization.key}>
-            <OrganizationsShortListItem
-              organization={organization}
-              skipOnboarding={skipOnboarding}
-            />
+            <OrganizationsShortListItem onClick={onClick} organization={organization} />
           </li>
         ))}
       </ul>
-      <div className="big-spacer-top">
-        <span className="big-spacer-right">
-          {translateWithParameters('x_of_y_shown', organizationsShown.length, organizations.length)}
-        </span>
-        {organizations.length > 3 && (
-          <Link className="small" onClick={skipOnboarding} to="/account/organizations">
+      {organizations.length > 3 && (
+        <div className="big-spacer-top">
+          <span className="big-spacer-right">
+            {translateWithParameters(
+              'x_of_y_shown',
+              organizationsShown.length,
+              organizations.length
+            )}
+          </span>
+          <Link className="small" onClick={onClick} to="/account/organizations">
             {translate('see_all')}
           </Link>
-        )}
-      </div>
+        </div>
+      )}
     </div>
   );
 }
index 49be2296cb9bd86a6232df39ff2a0ef89f36c72f..c9740ebd5e86f3e2bf0529156a5a211b2e299eb4 100644 (file)
@@ -24,15 +24,15 @@ import { withRouter, Router } from '../../../components/hoc/withRouter';
 import { getOrganizationUrl } from '../../../helpers/urls';
 
 interface Props {
+  onClick: () => void;
   organization: T.Organization;
   router: Router;
-  skipOnboarding: () => void;
 }
 
 export class OrganizationsShortListItem extends React.PureComponent<Props> {
   handleClick = () => {
-    const { organization, router, skipOnboarding } = this.props;
-    skipOnboarding();
+    const { onClick, organization, router } = this.props;
+    onClick();
     router.push(getOrganizationUrl(organization.key));
   };
 
index 2521fc0cba8c43fa8db3d8ff750c3b3252492b18..ab03de8c5d3cd02bbaebc69bf03eda94c1c7a050 100644 (file)
@@ -59,7 +59,6 @@ function shallowRender(props: Partial<Props> = {}) {
       currentUser={mockCurrentUser()}
       onClose={jest.fn()}
       onOpenProjectOnboarding={jest.fn()}
-      skipOnboarding={jest.fn()}
       userOrganizations={[]}
       {...props}
     />
index f0e9b24e4d30eb1bbaa99b090324cda94820e2dc..f5f46c188c7e563ae5d11581c7f078a2feb4a501 100644 (file)
@@ -50,7 +50,5 @@ it('should limit displayed orgs to the first three', () => {
 });
 
 function shallowRender(props: Partial<Props> = {}) {
-  return shallow(
-    <OrganizationsShortList organizations={[]} skipOnboarding={jest.fn()} {...props} />
-  );
+  return shallow(<OrganizationsShortList onClick={jest.fn()} organizations={[]} {...props} />);
 }
index bf84c7895ba0beed273c0ac3eeaf39a3db84dc98..acc2ba83e8f349b7d8405e45443ada8f5f16aae6 100644 (file)
@@ -27,23 +27,23 @@ it('renders correctly', () => {
   expect(shallowRender()).toMatchSnapshot();
 });
 
-it('calls skiponboarding and redirects to org page', () => {
-  const skipOnboarding = jest.fn();
+it('calls onClick and redirects to org page', () => {
+  const onClick = jest.fn();
   const push = jest.fn();
-  const wrapper = shallowRender({ skipOnboarding, router: mockRouter({ push }) });
+  const wrapper = shallowRender({ onClick, router: mockRouter({ push }) });
 
   click(wrapper);
 
-  expect(skipOnboarding).toHaveBeenCalledTimes(1);
+  expect(onClick).toHaveBeenCalledTimes(1);
   expect(push).toHaveBeenCalledWith('/organizations/foo');
 });
 
 function shallowRender(props: Partial<OrganizationsShortListItem['props']> = {}) {
   return shallow(
     <OrganizationsShortListItem
+      onClick={jest.fn()}
       organization={mockOrganization()}
       router={mockRouter()}
-      skipOnboarding={jest.fn()}
       {...props}
     />
   );
index 1c9eb96f30e526129541bd3ea8e182d0de294be7..bd1ed0ad54fbc1f2de55d4a102d71f96a8def499 100644 (file)
@@ -3,9 +3,9 @@
 exports[`renders correctly 1`] = `
 <Modal
   contentLabel="onboarding.header"
-  medium={true}
   onRequestClose={[MockFunction]}
   shouldCloseOnOverlayClick={false}
+  size="small"
 >
   <div
     className="modal-head"
@@ -28,11 +28,11 @@ exports[`renders correctly 1`] = `
       <OnboardingProjectIcon
         className="big-spacer-bottom"
       />
-      <h6
-        className="onboarding-choice-name big-spacer-bottom"
+      <h3
+        className="big-spacer-bottom"
       >
         onboarding.analyze_your_code
-      </h6>
+      </h3>
       <Button
         onClick={[MockFunction]}
       >
@@ -55,9 +55,9 @@ exports[`renders correctly 1`] = `
 exports[`should display organization list if any 1`] = `
 <Modal
   contentLabel="onboarding.header"
-  medium={true}
   onRequestClose={[MockFunction]}
   shouldCloseOnOverlayClick={false}
+  size="medium"
 >
   <div
     className="modal-head"
@@ -80,11 +80,11 @@ exports[`should display organization list if any 1`] = `
       <OnboardingProjectIcon
         className="big-spacer-bottom"
       />
-      <h6
-        className="onboarding-choice-name big-spacer-bottom"
+      <h3
+        className="big-spacer-bottom"
       >
         onboarding.analyze_your_code
-      </h6>
+      </h3>
       <Button
         onClick={[MockFunction]}
       >
@@ -104,12 +104,13 @@ exports[`should display organization list if any 1`] = `
       <OnboardingTeamIcon
         className="big-spacer-bottom"
       />
-      <h6
-        className="onboarding-choice-name big-spacer-bottom"
+      <h3
+        className="big-spacer-bottom"
       >
         onboarding.browse_your_organizations
-      </h6>
+      </h3>
       <OrganizationsShortList
+        onClick={[MockFunction]}
         organizations={
           Array [
             Object {
@@ -122,7 +123,6 @@ exports[`should display organization list if any 1`] = `
             },
           ]
         }
-        skipOnboarding={[MockFunction]}
       />
     </div>
   </div>
index 5410482f39513aba7e2e7814a7c5c0337305a9e3..fb1121921b8c7292ae1eb5a151bff7cdcb6a36a4 100644 (file)
@@ -2,46 +2,44 @@
 
 exports[`should limit displayed orgs to the first three 1`] = `
 <div>
-  <ul
-    className="account-projects-list"
-  >
+  <ul>
     <li
       key="bar"
     >
       <withRouter(OrganizationsShortListItem)
+        onClick={[MockFunction]}
         organization={
           Object {
             "key": "bar",
             "name": "Bar",
           }
         }
-        skipOnboarding={[MockFunction]}
       />
     </li>
     <li
       key="foo"
     >
       <withRouter(OrganizationsShortListItem)
+        onClick={[MockFunction]}
         organization={
           Object {
             "key": "foo",
             "name": "Foo",
           }
         }
-        skipOnboarding={[MockFunction]}
       />
     </li>
     <li
       key="kor"
     >
       <withRouter(OrganizationsShortListItem)
+        onClick={[MockFunction]}
         organization={
           Object {
             "key": "kor",
             "name": "Kor",
           }
         }
-        skipOnboarding={[MockFunction]}
       />
     </li>
   </ul>
@@ -68,44 +66,33 @@ exports[`should limit displayed orgs to the first three 1`] = `
 
 exports[`should render correctly 1`] = `
 <div>
-  <ul
-    className="account-projects-list"
-  >
+  <ul>
     <li
       key="bar"
     >
       <withRouter(OrganizationsShortListItem)
+        onClick={[MockFunction]}
         organization={
           Object {
             "key": "bar",
             "name": "Bar",
           }
         }
-        skipOnboarding={[MockFunction]}
       />
     </li>
     <li
       key="foo"
     >
       <withRouter(OrganizationsShortListItem)
+        onClick={[MockFunction]}
         organization={
           Object {
             "key": "foo",
             "name": "Foo",
           }
         }
-        skipOnboarding={[MockFunction]}
       />
     </li>
   </ul>
-  <div
-    className="big-spacer-top"
-  >
-    <span
-      className="big-spacer-right"
-    >
-      x_of_y_shown.2.2
-    </span>
-  </div>
 </div>
 `;
index 41cf98f083d80745a83bf2cb80aa6dbdb66fe223..2a7946f0f1c06cddbbfcbc7804500ce14678bcdf 100644 (file)
   cursor: pointer;
   outline: none;
 }
-
-.onboarding-choices {
-  display: flex;
-  justify-content: space-around;
-  padding: 44px 170px;
-  background-color: var(--barBackgroundColor);
-  margin-top: var(--pagePadding);
-}
-
-.onboarding-choice {
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-  padding: calc(2 * var(--gridSize));
-  width: 190px;
-  height: 190px;
-  background-color: #fff;
-  border: solid 1px #fff;
-  border-radius: 3px;
-  transition: all 0.2s ease;
-  box-shadow: 0 1px 1px 1px var(--barBorderColor);
-}
-
-.onboarding-choice svg {
-  color: var(--gray40);
-}
-
-.onboarding-choice-name {
-  color: inherit;
-  font-size: var(--mediumFontSize);
-}
-
-.onboarding-choice .note {
-  font-weight: 400;
-}
-
-.onboarding-choice:hover,
-.onboarding-choice:focus,
-.onboarding-choice:active {
-  background-color: #fff;
-  color: var(--darkBlue);
-  box-shadow: var(--defaultShadow);
-  transform: translateY(-2px);
-}
index 3bad28086b9614e5ff30ed0d5cf53300e3749df8..1e7b4cbe738924b2870210295f31b5bc87b1cbfb 100644 (file)
@@ -101,7 +101,7 @@ export default class PasswordForm extends React.PureComponent<Props, State> {
 
     const header = translate('my_profile.password.title');
     return (
-      <Modal contentLabel={header} onRequestClose={this.props.onClose}>
+      <Modal contentLabel={header} onRequestClose={this.props.onClose} size="small">
         <form autoComplete="off" id="user-password-form" onSubmit={this.handleChangePassword}>
           <header className="modal-head">
             <h2>{header}</h2>
index c62c2f253deedf23132a315f8bd95e8bbc9113d3..ef7b4103bf188e3e783d94299ff1277c39ec43e4 100644 (file)
@@ -154,7 +154,7 @@ export default class TokensForm extends React.PureComponent<Props, State> {
           id="generate-token-form"
           onSubmit={this.handleGenerateToken}>
           <input
-            className="spacer-right"
+            className="input-large spacer-right"
             maxLength={100}
             onChange={this.handleNewTokenChange}
             placeholder={translate('users.enter_token_name')}
index a96a27ea7c0659674f0d63fb4567254e9d66868e..e5ec88fab7c064831320b7c696b3addbf608a375 100644 (file)
@@ -153,7 +153,7 @@ export default class UserForm extends React.PureComponent<Props, State> {
 
     const header = user ? translate('users.update_user') : translate('users.create_user');
     return (
-      <Modal contentLabel={header} onRequestClose={this.props.onClose}>
+      <Modal contentLabel={header} onRequestClose={this.props.onClose} size="small">
         <form
           autoComplete="off"
           id="user-form"
index 331e642bd8e44c9119bcfc7980782604cf6ff314..a39e72ec8e38a63dc493e6c13767e7eb6579087a 100644 (file)
@@ -35,7 +35,7 @@ export default class UserScmAccountInput extends React.PureComponent<Props> {
 
   render() {
     return (
-      <div className="js-scm-account">
+      <div className="js-scm-account display-flex-row spacer-bottom">
         <input
           maxLength={255}
           onChange={this.handleChange}
index 2a6ae4b6be1ea3f85452532dc8f4103637883263..401bae31b0a7adb4063f277b0e6fae4a33ed5ec8 100644 (file)
@@ -14,7 +14,7 @@ exports[`should render correctly 1`] = `
     onSubmit={[Function]}
   >
     <input
-      className="spacer-right"
+      className="input-large spacer-right"
       maxLength={100}
       onChange={[Function]}
       placeholder="users.enter_token_name"
@@ -90,7 +90,7 @@ exports[`should render correctly 2`] = `
     onSubmit={[Function]}
   >
     <input
-      className="spacer-right"
+      className="input-large spacer-right"
       maxLength={100}
       onChange={[Function]}
       placeholder="users.enter_token_name"
index 0d43e06afd015295beb25da9f3081f694a4230d5..d8a830062c93890ff21876405244f33a6d464a4f 100644 (file)
@@ -74,6 +74,7 @@ export default class CreateWebhookForm extends React.PureComponent<Props> {
         isInitialValid={isUpdate}
         onClose={this.props.onClose}
         onSubmit={this.props.onDone}
+        size="small"
         validate={this.handleValidate}>
         {({
           dirty,
index 489d1aecb6ffc58d162e281bd79743b79452af45..b9b3eda1ca30cb8c3613851fc17e34aac9d3acee 100644 (file)
@@ -13,6 +13,7 @@ exports[`should render correctly when creating a new webhook 1`] = `
   isInitialValid={false}
   onClose={[MockFunction]}
   onSubmit={[MockFunction]}
+  size="small"
   validate={[Function]}
 >
   <Component />
@@ -32,6 +33,7 @@ exports[`should render correctly when updating a webhook 1`] = `
   isInitialValid={true}
   onClose={[MockFunction]}
   onSubmit={[MockFunction]}
+  size="small"
   validate={[Function]}
 >
   <Component />
index e34b22faf4dead3bd4ba1e9a2bcbc3b04e6d8a46..9f6834cfa6513e7f2292744b3871973517cb99c6 100644 (file)
@@ -388,7 +388,7 @@ export default class MeasuresOverlay extends React.PureComponent<Props, State> {
     const { loading } = this.state;
 
     return (
-      <Modal contentLabel="" large={true} onRequestClose={this.props.onClose}>
+      <Modal contentLabel="" onRequestClose={this.props.onClose} size={'large'}>
         <div className="modal-container source-viewer-measures-modal">
           <div className="source-viewer-header-component source-viewer-measures-component">
             <div className="source-viewer-header-component-project">
index 71583aa014bcb7504f69e98bcdfa6e0de5756f2b..fb658fc95a27b66f0666bde0bc7ad66bbad03dd4 100644 (file)
@@ -3,8 +3,8 @@
 exports[`should render source file 1`] = `
 <Modal
   contentLabel=""
-  large={true}
   onRequestClose={[MockFunction]}
+  size="large"
 >
   <div
     className="modal-container source-viewer-measures-modal"
@@ -362,8 +362,8 @@ exports[`should render source file 1`] = `
 exports[`should render source file 2`] = `
 <Modal
   contentLabel=""
-  large={true}
   onRequestClose={[MockFunction]}
+  size="large"
 >
   <div
     className="modal-container source-viewer-measures-modal"
@@ -1327,8 +1327,8 @@ exports[`should render source file 2`] = `
 exports[`should render test file 1`] = `
 <Modal
   contentLabel=""
-  large={true}
   onRequestClose={[MockFunction]}
+  size="large"
 >
   <div
     className="modal-container source-viewer-measures-modal"
index 1007498f19bae3c32946754fed557fd559b510f2..2c99b2f74a428604d2f0b35001798dc8ed30db25 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
+import * as classNames from 'classnames';
 import { getMarkdownHelpUrl } from '../../helpers/urls';
 import { translate } from '../../helpers/l10n';
 
-export default class MarkdownTips extends React.PureComponent {
+interface Props {
+  className?: string;
+}
+
+export default class MarkdownTips extends React.PureComponent<Props> {
   handleClick(evt: React.SyntheticEvent<HTMLAnchorElement>) {
     evt.preventDefault();
     window.open(getMarkdownHelpUrl(), 'Markdown', 'height=300,width=600,scrollbars=1,resizable=1');
@@ -29,7 +34,7 @@ export default class MarkdownTips extends React.PureComponent {
 
   render() {
     return (
-      <div className="markdown-tips">
+      <div className={classNames('markdown-tips', this.props.className)}>
         <a className="little-spacer-right" href="#" onClick={this.handleClick}>
           {translate('markdown.helplink')}
         </a>
index e9e6a94f4b4751b0f579d94af499be98eba2e527..a1df816ed01508168f3266c0188cb5dd1a3cb646 100644 (file)
@@ -29,6 +29,7 @@ interface Props {
   id?: string;
   loading?: boolean;
   onCheck: (checked: boolean, id?: string) => void;
+  right?: boolean;
   thirdState?: boolean;
 }
 
@@ -46,31 +47,33 @@ export default class Checkbox extends React.PureComponent<Props> {
   };
 
   render() {
+    const { children, disabled, loading, right } = this.props;
     const className = classNames('icon-checkbox', {
       'icon-checkbox-checked': this.props.checked,
       'icon-checkbox-single': this.props.thirdState,
-      'icon-checkbox-disabled': this.props.disabled
+      'icon-checkbox-disabled': disabled
     });
 
-    if (this.props.children) {
+    if (children) {
       return (
         <a
           className={classNames('link-checkbox', this.props.className, {
-            note: this.props.disabled,
-            disabled: this.props.disabled
+            note: disabled,
+            disabled
           })}
           href="#"
           id={this.props.id}
           onClick={this.handleClick}>
-          <DeferredSpinner loading={Boolean(this.props.loading)}>
+          {right && children}
+          <DeferredSpinner loading={Boolean(loading)}>
             <i className={className} />
           </DeferredSpinner>
-          {this.props.children}
+          {!right && children}
         </a>
       );
     }
 
-    if (this.props.loading) {
+    if (loading) {
       return <DeferredSpinner />;
     }
 
index a86ac592a730669426b9b196aaf5e87d49c54798..2b0187096302190d4ddfcf2700ce7816c6b30b18 100644 (file)
@@ -94,8 +94,8 @@ export default class ConfirmModal<T = string> extends React.PureComponent<Props<
   };
 
   render() {
-    const { header, onClose, medium, noBackdrop, large } = this.props;
-    const modalProps = { header, onClose, medium, noBackdrop, large };
+    const { header, onClose, noBackdrop, size } = this.props;
+    const modalProps = { header, onClose, noBackdrop, size };
     return (
       <SimpleModal onSubmit={this.handleSubmit} {...modalProps}>
         {this.renderModalContent}
index e3beac1109bd4a0960b04418b903d706b9faf61b..c250470f9af11104ced61a98c7f0b41fed6683d9 100644 (file)
 import * as React from 'react';
 import * as ReactModal from 'react-modal';
 import * as classNames from 'classnames';
-import { isSonarCloud } from '../../helpers/system';
 
 ReactModal.setAppElement('#content');
 
 export interface ModalProps {
   children: React.ReactNode;
-  medium?: boolean;
+  size?: 'small' | 'medium' | 'large';
   noBackdrop?: boolean;
-  large?: boolean;
 }
 
 type MandatoryProps = Pick<ReactModal.Props, 'contentLabel'>;
@@ -38,16 +36,11 @@ type Props = Partial<ReactModal.Props> & MandatoryProps & ModalProps;
 export default function Modal(props: Props) {
   return (
     <ReactModal
-      className={classNames(
-        'modal',
-        {
-          sonarcloud: isSonarCloud()
-        },
-        {
-          'modal-medium': props.medium,
-          'modal-large': props.large
-        }
-      )}
+      className={classNames('modal', {
+        'modal-small': props.size === 'small',
+        'modal-medium': props.size === 'medium',
+        'modal-large': props.size === 'large'
+      })}
       isOpen={true}
       overlayClassName={classNames('modal-overlay', { 'modal-no-backdrop': props.noBackdrop })}
       {...props}
index e389c71d664b52d95f9342d095cc441ba7338d61..ba030d96b61529c658220967861dd9fdb4a5f0d3 100644 (file)
@@ -38,10 +38,7 @@ export default class Radio extends React.PureComponent<Props> {
     return (
       <a
         aria-checked={this.props.checked}
-        className={classNames(
-          'display-inline-flex-center link-base-color link-no-underline',
-          this.props.className
-        )}
+        className={classNames('display-inline-flex-center link-checkbox', this.props.className)}
         href="#"
         onClick={this.handleClick}
         role="radio">
index 3a78bd4ea423eb0fb1c50b0ab042d57e59093de5..43558ccdf90d91cf8f48d973cbf25a5f32255a04 100644 (file)
@@ -25,6 +25,7 @@ import { translate, translateWithParameters } from '../../helpers/l10n';
 interface Props<T> {
   autofocus?: boolean;
   canCreate?: boolean;
+  className?: string;
   clearable?: boolean;
   defaultOptions?: T[];
   minimumQueryLength?: number;
@@ -126,7 +127,7 @@ export default class SearchSelect<T extends { value: string }> extends React.Pur
     return (
       <Component
         autoFocus={this.autofocus}
-        className="input-super-large"
+        className={this.props.className}
         clearable={this.props.clearable}
         escapeClearsValue={false}
         filterOption={this.handleFilterOption}
index d41389567521e0f578fb325c0220d787e832b925..d359d8daebffaef92b2fd7151188654ce4184c05 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import Modal from './Modal';
+import Modal, { ModalProps } from './Modal';
 import ValidationForm, { ChildrenProps } from './ValidationForm';
 import DeferredSpinner from '../common/DeferredSpinner';
 import { SubmitButton, ResetButtonLink } from '../ui/buttons';
 import { translate } from '../../helpers/l10n';
 
-interface Props<V> {
+interface Props<V> extends ModalProps {
   children: (props: ChildrenProps<V>) => React.ReactNode;
   confirmButtonText: string;
   header: string;
@@ -44,7 +44,11 @@ export default class ValidationModal<V> extends React.PureComponent<Props<V>> {
 
   render() {
     return (
-      <Modal contentLabel={this.props.header} onRequestClose={this.props.onClose}>
+      <Modal
+        contentLabel={this.props.header}
+        noBackdrop={this.props.noBackdrop}
+        onRequestClose={this.props.onClose}
+        size={this.props.size}>
         <ValidationForm
           initialValues={this.props.initialValues}
           isInitialValid={this.props.isInitialValid}
index a0985d44a8435fb72be986745592cba66b74bce1..f089b43dfa585cf762bee9a7983f80108b85dd09 100644 (file)
@@ -102,3 +102,7 @@ it('should apply custom class', () => {
   );
   expect(checkbox.is('.customclass')).toBeTruthy();
 });
+
+it('should render the checkbox on the right', () => {
+  expect(shallow(<Checkbox checked={true} onCheck={() => true} right={true} />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Checkbox-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Checkbox-test.tsx.snap
new file mode 100644 (file)
index 0000000..f733f88
--- /dev/null
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render the checkbox on the right 1`] = `
+<a
+  className="icon-checkbox icon-checkbox-checked"
+  href="#"
+  onClick={[Function]}
+/>
+`;
index 92e8076ce5e262a9a5edaaf833f14788ea85a252..cd6bc65b0809212fb37cb23897437ee60b83f716 100644 (file)
@@ -3,7 +3,7 @@
 exports[`should render and check 1`] = `
 <a
   aria-checked={false}
-  className="display-inline-flex-center link-base-color link-no-underline"
+  className="display-inline-flex-center link-checkbox"
   href="#"
   onClick={[Function]}
   role="radio"
@@ -17,7 +17,7 @@ exports[`should render and check 1`] = `
 exports[`should render and check 2`] = `
 <a
   aria-checked={true}
-  className="display-inline-flex-center link-base-color link-no-underline"
+  className="display-inline-flex-center link-checkbox"
   href="#"
   onClick={[Function]}
   role="radio"
index 9f99087e010b405c49b3c3b6346cf27b2de15e9b..792343f1a73b4c44164291040a613516eafb6fc2 100644 (file)
@@ -3,7 +3,6 @@
 exports[`should render Select 1`] = `
 <Select
   autoFocus={true}
-  className="input-super-large"
   escapeClearsValue={false}
   filterOption={[Function]}
   isLoading={false}
index 0d6371cfaefd08819318e54ab5828897557e6c00..f0e0f2db5834c0f4e486dfba8e1560de8b4da9ac 100644 (file)
  */
 import * as React from 'react';
 import Icon, { IconProps } from './Icon';
+import * as theme from '../../app/theme';
 
 export default function OnboardingAddMembersIcon({
   className,
-  fill = 'currentColor',
+  fill = theme.darkBlue,
   size = 64
 }: IconProps) {
   return (
diff --git a/server/sonar-web/src/main/js/components/ui/NewInfoBox.css b/server/sonar-web/src/main/js/components/ui/NewInfoBox.css
deleted file mode 100644 (file)
index 0c8b0ca..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-.new-info-box {
-  display: flex;
-  padding: var(--gridSize);
-  background-color: var(--veryLightBlue);
-  border: 1px solid var(--alertBorderInfo);
-  border-radius: 2px;
-}
-
-.new-info-box-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
diff --git a/server/sonar-web/src/main/js/components/ui/NewInfoBox.tsx b/server/sonar-web/src/main/js/components/ui/NewInfoBox.tsx
deleted file mode 100644 (file)
index c9a2da3..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import * as React from 'react';
-import * as classNames from 'classnames';
-import { ButtonIcon } from './buttons';
-import ClearIcon from '../icons-components/ClearIcon';
-import { sonarcloudBlack500 } from '../../app/theme';
-import { translate } from '../../helpers/l10n';
-import './NewInfoBox.css';
-
-export interface Props {
-  children: React.ReactNode;
-  className?: string;
-  description: React.ReactNode;
-  onClose?: () => void;
-  title: string;
-}
-
-export default function NewInfoBox({ className, children, description, onClose, title }: Props) {
-  return (
-    <div className={classNames('new-info-box', className)} role="alert">
-      <div className="new-info-box-inner text-left">
-        <div className="new-info-box-header spacer-bottom">
-          <span className="display-inline-flex-center">
-            <span className="badge badge-new spacer-right">{translate('new')}</span>
-            <strong>{title}</strong>
-          </span>
-        </div>
-        <p className="note spacer-bottom">{description}</p>
-        {children}
-      </div>
-      <ButtonIcon className="button-small spacer-left" color={sonarcloudBlack500} onClick={onClose}>
-        <ClearIcon size={12} />
-      </ButtonIcon>
-    </div>
-  );
-}
diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/NewInfoBox-test.tsx b/server/sonar-web/src/main/js/components/ui/__tests__/NewInfoBox-test.tsx
deleted file mode 100644 (file)
index d7fc93c..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import * as React from 'react';
-import { shallow } from 'enzyme';
-import NewInfoBox from '../NewInfoBox';
-import { click } from '../../../helpers/testUtils';
-
-it('should render correctly', () => {
-  expect(
-    shallow(
-      <NewInfoBox description="My description" onClose={jest.fn()} title="My title">
-        <div />
-      </NewInfoBox>
-    )
-  ).toMatchSnapshot();
-});
-
-it('should allow to opt out', () => {
-  const onClose = jest.fn();
-  const wrapper = shallow(
-    <NewInfoBox description="" onClose={onClose} title="">
-      <div />
-    </NewInfoBox>
-  );
-  click(wrapper.find('ButtonIcon'));
-  expect(onClose).toHaveBeenCalled();
-});
diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/NewInfoBox-test.tsx.snap b/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/NewInfoBox-test.tsx.snap
deleted file mode 100644 (file)
index fca669b..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<div
-  className="new-info-box"
-  role="alert"
->
-  <div
-    className="new-info-box-inner text-left"
-  >
-    <div
-      className="new-info-box-header spacer-bottom"
-    >
-      <span
-        className="display-inline-flex-center"
-      >
-        <span
-          className="badge badge-new spacer-right"
-        >
-          new
-        </span>
-        <strong>
-          My title
-        </strong>
-      </span>
-    </div>
-    <p
-      className="note spacer-bottom"
-    >
-      My description
-    </p>
-    <div />
-  </div>
-  <ButtonIcon
-    className="button-small spacer-left"
-    color="#8a8c8f"
-    onClick={[MockFunction]}
-  >
-    <ClearIcon
-      size={12}
-    />
-  </ButtonIcon>
-</div>
-`;
index cede4ec4d1078a505abc4e11a17029187864f403..93b755a223039a88b2b8db3365c1e97fa1e0b9ab 100644 (file)
   font-size: var(--mediumFontSize);
 }
 
+.button-huge {
+  flex-direction: column;
+  padding: calc(2 * var(--gridSize));
+  width: 200px;
+  height: 200px;
+  background-color: #fff;
+  border: solid 1px #fff;
+  border-radius: 3px;
+  transition: all 0.2s ease;
+  box-shadow: 0 1px 1px 1px var(--barBorderColor);
+}
+
+.button-huge:hover,
+.button-huge:focus,
+.button-huge:active {
+  background-color: #fff;
+  color: var(--darkBlue);
+  box-shadow: var(--defaultShadow);
+  transform: translateY(-2px);
+}
+
 /* #region .button-group */
 .button-group {
   display: inline-block;
 /* #endregion */
 
 .button-list {
+  display: inline-flex;
+  justify-content: space-between;
   height: auto;
   border: 1px solid var(--barBorderColor);
   padding: var(--gridSize);
   margin: calc(var(--gridSize) / 2);
-  text-align: left;
-  justify-content: space-between;
   color: var(--secondFontColor);
   font-weight: normal;
 }
   background-color: white;
   border-color: var(--blue);
   color: var(--darkBlue);
-  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.175);
 }
index 99e4c327a62e2b84f3171777f3f805d578531ea6..5ad0b9197ef8a306b3436ec54edf006d0dbd5426 100644 (file)
 import * as React from 'react';
 import * as classNames from 'classnames';
 import * as theme from '../../app/theme';
+import ChevronRightIcon from '../icons-components/ChevronRightcon';
 import ClearIcon from '../icons-components/ClearIcon';
 import EditIcon from '../icons-components/EditIcon';
 import Tooltip from '../controls/Tooltip';
 import './buttons.css';
-import ChevronRightIcon from '../icons-components/ChevronRightcon';
 
 type AllowedButtonAttributes = Pick<
   React.ButtonHTMLAttributes<HTMLButtonElement>,
index bd37528c205bf068edfc0642af0a0aefa024a739..a6e20a88178dabcce9f63b5dd344d8f0e75c458b 100644 (file)
@@ -21,6 +21,17 @@ import { InjectedRouter } from 'react-router';
 import { Location } from 'history';
 import { Profile } from '../apps/quality-profiles/types';
 
+export function mockAlmApplication(overrides: Partial<T.AlmApplication> = {}): T.AlmApplication {
+  return {
+    backgroundColor: '#0052CC',
+    iconPath: '"/static/authbitbucket/bitbucket.svg"',
+    installationUrl: 'https://bitbucket.org/install/app',
+    key: 'bitbucket',
+    name: 'BitBucket',
+    ...overrides
+  };
+}
+
 export function mockAlmOrganization(overrides: Partial<T.AlmOrganization> = {}): T.AlmOrganization {
   return {
     avatar: 'http://example.com/avatar',
@@ -90,6 +101,52 @@ export function mockEvent(overrides = {}) {
   } as any;
 }
 
+export function mockIssue(withLocations = false, overrides: Partial<T.Issue> = {}) {
+  const issue: T.Issue = {
+    actions: [],
+    component: 'main.js',
+    componentLongName: 'main.js',
+    componentQualifier: 'FIL',
+    componentUuid: 'foo1234',
+    creationDate: '2017-03-01T09:36:01+0100',
+    flows: [],
+    fromHotspot: false,
+    key: 'AVsae-CQS-9G3txfbFN2',
+    line: 25,
+    message: 'Reduce the number of conditional operators (4) used in the expression',
+    organization: 'myorg',
+    project: 'myproject',
+    projectKey: 'foo',
+    projectName: 'Foo',
+    projectOrganization: 'org',
+    rule: 'javascript:S1067',
+    ruleName: 'foo',
+    secondaryLocations: [],
+    severity: 'MAJOR',
+    status: 'OPEN',
+    textRange: { startLine: 25, endLine: 26, startOffset: 0, endOffset: 15 },
+    transitions: [],
+    type: 'BUG'
+  };
+
+  function loc(): T.FlowLocation {
+    return {
+      component: 'main.js',
+      textRange: { startLine: 1, startOffset: 1, endLine: 2, endOffset: 2 }
+    };
+  }
+
+  if (withLocations) {
+    issue.flows = [[loc(), loc(), loc()], [loc(), loc()]];
+    issue.secondaryLocations = [loc(), loc()];
+  }
+
+  return {
+    ...issue,
+    ...overrides
+  };
+}
+
 export function mockLocation(overrides: Partial<Location> = {}): Location {
   return {
     action: 'PUSH',
@@ -123,6 +180,14 @@ export function mockOrganizationWithAlm(
   });
 }
 
+export function mockQualityGate(overrides: Partial<T.QualityGate> = {}): T.QualityGate {
+  return {
+    id: 1,
+    name: 'qualitygate',
+    ...overrides
+  };
+}
+
 export function mockQualityProfile(overrides: Partial<Profile> = {}): Profile {
   return {
     activeDeprecatedRuleCount: 2,
@@ -157,48 +222,17 @@ export function mockRouter(overrides: { push?: Function; replace?: Function } =
   } as InjectedRouter;
 }
 
-export function mockIssue(withLocations = false, overrides: Partial<T.Issue> = {}) {
-  const issue: T.Issue = {
-    actions: [],
-    component: 'main.js',
-    componentLongName: 'main.js',
-    componentQualifier: 'FIL',
-    componentUuid: 'foo1234',
-    creationDate: '2017-03-01T09:36:01+0100',
-    flows: [],
-    fromHotspot: false,
-    key: 'AVsae-CQS-9G3txfbFN2',
-    line: 25,
-    message: 'Reduce the number of conditional operators (4) used in the expression',
-    organization: 'myorg',
-    project: 'myproject',
-    projectKey: 'foo',
-    projectName: 'Foo',
-    projectOrganization: 'org',
-    rule: 'javascript:S1067',
-    ruleName: 'foo',
-    secondaryLocations: [],
-    severity: 'MAJOR',
-    status: 'OPEN',
-    textRange: { startLine: 25, endLine: 26, startOffset: 0, endOffset: 15 },
-    transitions: [],
-    type: 'BUG'
-  };
-
-  function loc(): T.FlowLocation {
-    return {
-      component: 'main.js',
-      textRange: { startLine: 1, startOffset: 1, endLine: 2, endOffset: 2 }
-    };
-  }
-
-  if (withLocations) {
-    issue.flows = [[loc(), loc(), loc()], [loc(), loc()]];
-    issue.secondaryLocations = [loc(), loc()];
-  }
-
+export function mockRule(overrides: Partial<T.Rule> = {}): T.Rule {
   return {
-    ...issue,
+    key: 'javascript:S1067',
+    lang: 'js',
+    langName: 'JavaScript',
+    name: 'Use foo',
+    severity: 'MAJOR',
+    status: 'READY',
+    sysTags: ['a', 'b'],
+    tags: ['x'],
+    type: 'CODE_SMELL',
     ...overrides
-  };
+  } as T.Rule;
 }
index 33a2cbb30af74d27eebe90b98709476ef89cfba6..605b0e96f0376bebd3534dd1b03db8f0367874bc 100644 (file)
@@ -1201,7 +1201,7 @@ quality_profiles.default_permissions=Users with the global "Manage Quality Profi
 quality_profiles.grant_permissions_to_more_users=Grant permissions to more users
 quality_profiles.grant_permissions_to_user_or_group=Grant permissions to a user or a group
 quality_profiles.additional_user_groups=Additional users / groups:
-quality_profiles.search_description=Search users by login or name, and groups by name
+quality_profiles.search_description=Search users by login or name, and groups by name:
 
 
 
@@ -2679,21 +2679,20 @@ organization.members.manage_a_team=Manage a team
 organization.members.add_to_members=Add to members
 organization.members.config_synchro=Configure Synchronization
 organization.members.auto_sync_with_x=Automatic sync with {0}
-organization.members.auto_sync_members_from_org_x=Members can be synchronized automatically from your {0}
+organization.members.auto_sync_members_from_org_x=Now your members can be automatically synchronized with your {0}.
 organization.members.auto_sync_total_help.bitbucket=You might not see all members from your Bitbucket team yet, as they need to reconnect to SonarCloud to be members of the organization.
 organization.members.auto_sync_total_help.github=You might not see all members from your GitHub organization yet, as they need to connect to SonarCloud at least once to appear in this list.
 organization.members.see_all_members_on_x=See all members on {0}
 organization.members.management.title=Members Management
-organization.members.management.description=Select your management mode for members of this organization
+organization.members.management.description=Select your management mode for members of this organization.
 organization.members.management.manual=Manual
-organization.members.management.manual.add_members_manually=Admin add members manually from Sonarcloud existing users
-organization.members.management.manual.choose_members_permissions=Admin chooses each member permissions
+organization.members.management.manual.add_members_manually=Admin add members manually from SonarCloud existing users
 organization.members.management.automatic=Automatic sync with {0}
 organization.members.management.automatic.synchronized_from_x=Members are synchronized automatically from your {0}
 organization.members.management.automatic.members_changes_reflected.bitbucket=Your team members must reconnect to SonarCloud to be automatically added to correct SonarCloud organization
-organization.members.management.automatic.members_changes_reflected.github=If you add or remove a member on GitHub, SonarCloud immediately reflect the changes
-organization.members.management.automatic.still_choose_members_permissions=Admin still manages permissions for each member in SonarCloud
+organization.members.management.automatic.members_changes_reflected.github=If you add or remove a member on GitHub, SonarCloud immediately reflects the changes
 organization.members.management.automatic.warning=This will override your current Members and Permissions configuration
+organization.members.management.choose_members_permissions=Admin manages permissions for each member in SonarCloud
 organization.paid_plan.badge=Paid plan
 organization.default_visibility_of_new_projects=Default visibility of new projects:
 organization.change_visibility_form.header=Set Default Visibility of New Projects
@@ -2839,7 +2838,6 @@ onboarding.import_organization.create_new=Create new SonarCloud organization fro
 onboarding.import_organization.already_bound_x=Your organization {avatar} {name} is already bound to the SonarCloud organization {boundAvatar} {boundName}. Try again and choose a different organization.
 onboarding.import_organization.members_sync_info_x=All members from your {0} {1} will be added to your SonarCloud organization. As they connect to SonarCloud with their {2} account, members will automatically have access to your SonarCloud organization and its projects.
 onboarding.import_organization.bind_members_not_sync_info_x=We'll keep your members, groups and permissions as they are today on SonarCloud. To sync your members with your {0}, enable members sync in your Members tab.
-onboarding.import_organization.see_who_has_access=See who will have access
 onboarding.import_organization_x=Import {avatar} {name} into a SonarCloud organization
 onboarding.import_personal_organization_x=Bind {avatar} {name} with your personal SonarCloud organization {personalAvatar} {personalName}