]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10022 Grouping actions must be consistent
authorStas Vilchik <stas.vilchik@sonarsource.com>
Tue, 31 Oct 2017 15:26:09 +0000 (16:26 +0100)
committerStas Vilchik <stas.vilchik@sonarsource.com>
Fri, 3 Nov 2017 13:28:18 +0000 (14:28 +0100)
47 files changed:
server/sonar-web/src/main/js/app/styles/components/dropdowns.css
server/sonar-web/src/main/js/apps/background-tasks/components/Stats.js
server/sonar-web/src/main/js/apps/background-tasks/components/TaskActions.tsx
server/sonar-web/src/main/js/apps/background-tasks/components/Workers.tsx
server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/Workers-test.tsx
server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskActions-test.tsx.snap
server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/Workers-test.tsx.snap
server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list-item.hbs
server/sonar-web/src/main/js/apps/groups/templates/groups-list-item.hbs
server/sonar-web/src/main/js/apps/metrics/templates/metrics-list-item.hbs
server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.js.snap
server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.js
server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js
server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.js
server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.js
server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.js.snap
server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/RemoveMemberForm-test.js.snap
server/sonar-web/src/main/js/apps/permission-templates/components/ActionsCell.js
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js
server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.js
server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveAnalysisForm.js
server/sonar-web/src/main/js/apps/projectBranches/components/BranchRow.tsx
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchRow-test.tsx.snap
server/sonar-web/src/main/js/apps/projectsManagement/Header.tsx
server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Header-test.tsx.snap
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx
server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/ProfileActions-test.tsx.snap
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.tsx
server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.tsx
server/sonar-web/src/main/js/apps/settings/components/inputs/MultiValueInput.js
server/sonar-web/src/main/js/apps/settings/components/inputs/PropertySetInput.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/ProjectKeyStep.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/ProjectKeyStep-test.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/ProjectKeyStep-test.js.snap
server/sonar-web/src/main/js/apps/users/templates/users-deactivate.hbs
server/sonar-web/src/main/js/apps/users/templates/users-list-item.hbs
server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/components/IssueCommentLine.js
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueCommentLine-test.js.snap
server/sonar-web/src/main/js/helpers/handlebars/settingsIcon.js [new file with mode: 0644]
tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_restore.html
tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_restore.html
tests/src/test/resources/user/ExternalAuthenticationTest/create-and-delete-user.html
tests/src/test/resources/user/UsersPageTest/admin_should_change_its_own_password.html

index e11684d5660417b13ba2c6a14e93f6e2a6e39801..6de1a659782332e1fb21cda49177d0b74758169f 100644 (file)
   transition: none;
 }
 
+.dropdown-menu > li > a.text-danger,
+.dropdown-menu > li > a.text-danger:hover {
+  color: var(--red) !important;
+}
+
 .dropdown-menu .divider {
   height: 1px;
   margin: 6px 0;
index 22c58b2902a1a2ec3471e467b355dcdd00674539..8c239e9d8127f40f3332928e03c405de058c6699 100644 (file)
@@ -20,6 +20,7 @@
 /* @flow */
 import React from 'react';
 import Tooltip from '../../../components/controls/Tooltip';
+import DeleteIcon from '../../../components/icons-components/DeleteIcon';
 import { translate } from '../../../helpers/l10n';
 
 /*::
@@ -65,10 +66,11 @@ export default class Stats extends React.PureComponent {
           {this.props.isSystemAdmin && (
             <Tooltip overlay={translate('background_tasks.cancel_all_tasks')}>
               <a
-                className="js-cancel-pending icon-delete spacer-left"
+                className="js-cancel-pending spacer-left link-no-underline"
                 href="#"
-                onClick={this.handleCancelAllPending}
-              />
+                onClick={this.handleCancelAllPending}>
+                <DeleteIcon className="text-text-top" />
+              </a>
             </Tooltip>
           )}
         </span>
index d6510541fc2c902fc785d035833555bb0f0512fe..d944ebba4ef08a9d6592899db4470bb51a37ca95 100644 (file)
@@ -23,6 +23,7 @@ import Stacktrace from './Stacktrace';
 import { STATUSES } from './../constants';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { Task } from '../types';
+import ActionsDropdown, { ActionsDropdownItem } from '../../../components/controls/ActionsDropdown';
 
 interface Props {
   component?: {};
@@ -42,25 +43,21 @@ export default class TaskActions extends React.PureComponent<Props, State> {
     stacktraceOpen: false
   };
 
-  handleFilterClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
-    event.preventDefault();
+  handleFilterClick = () => {
     this.props.onFilterTask(this.props.task);
   };
 
-  handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
-    event.preventDefault();
+  handleCancelClick = () => {
     this.props.onCancelTask(this.props.task);
   };
 
-  handleShowScannerContextClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
-    event.preventDefault();
+  handleShowScannerContextClick = () => {
     this.setState({ scannerContextOpen: true });
   };
 
   closeScannerContext = () => this.setState({ scannerContextOpen: false });
 
-  handleShowStacktraceClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
-    event.preventDefault();
+  handleShowStacktraceClick = () => {
     this.setState({ stacktraceOpen: true });
   };
 
@@ -80,55 +77,39 @@ export default class TaskActions extends React.PureComponent<Props, State> {
 
     return (
       <td className="thin nowrap">
-        <div className="dropdown js-task-action">
-          <button className="dropdown-toggle" data-toggle="dropdown">
-            <i className="icon-dropdown" />
-          </button>
-          <ul className="dropdown-menu dropdown-menu-right">
-            {canFilter &&
-              task.componentName && (
-                <li>
-                  <a className="js-task-filter" href="#" onClick={this.handleFilterClick}>
-                    <i className="spacer-right icon-filter icon-gray" />
-                    {translateWithParameters(
-                      'background_tasks.filter_by_component_x',
-                      task.componentName
-                    )}
-                  </a>
-                </li>
-              )}
-            {canCancel && (
-              <li>
-                <a className="js-task-cancel" href="#" onClick={this.handleCancelClick}>
-                  <i className="spacer-right icon-delete" />
-                  {translate('background_tasks.cancel_task')}
-                </a>
-              </li>
+        <ActionsDropdown className="js-task-action">
+          {canFilter &&
+            task.componentName && (
+              <ActionsDropdownItem className="js-task-filter" onClick={this.handleFilterClick}>
+                {translateWithParameters(
+                  'background_tasks.filter_by_component_x',
+                  task.componentName
+                )}
+              </ActionsDropdownItem>
             )}
-            {task.hasScannerContext && (
-              <li>
-                <a
-                  className="js-task-show-scanner-context"
-                  href="#"
-                  onClick={this.handleShowScannerContextClick}>
-                  <i className="spacer-right icon-list icon-gray" />
-                  {translate('background_tasks.show_scanner_context')}
-                </a>
-              </li>
-            )}
-            {canShowStacktrace && (
-              <li>
-                <a
-                  className="js-task-show-stacktrace"
-                  href="#"
-                  onClick={this.handleShowStacktraceClick}>
-                  <i className="spacer-right icon-list icon-red" />
-                  {translate('background_tasks.show_stacktrace')}
-                </a>
-              </li>
-            )}
-          </ul>
-        </div>
+          {canCancel && (
+            <ActionsDropdownItem
+              className="js-task-cancel"
+              destructive={true}
+              onClick={this.handleCancelClick}>
+              {translate('background_tasks.cancel_task')}
+            </ActionsDropdownItem>
+          )}
+          {task.hasScannerContext && (
+            <ActionsDropdownItem
+              className="js-task-show-scanner-context"
+              onClick={this.handleShowScannerContextClick}>
+              {translate('background_tasks.show_scanner_context')}
+            </ActionsDropdownItem>
+          )}
+          {canShowStacktrace && (
+            <ActionsDropdownItem
+              className="js-task-show-stacktrace"
+              onClick={this.handleShowStacktraceClick}>
+              {translate('background_tasks.show_stacktrace')}
+            </ActionsDropdownItem>
+          )}
+        </ActionsDropdown>
 
         {this.state.scannerContextOpen && (
           <ScannerContext onClose={this.closeScannerContext} task={task} />
index ee8225ac72247408106e52b90070819a5dc8160b..a5eef35810f47a80e4844ed2a2b733af61cbf397 100644 (file)
@@ -26,6 +26,7 @@ import { getWorkers } from '../../../api/ce';
 import { translate } from '../../../helpers/l10n';
 import HelpIcon from '../../../components/icons-components/HelpIcon';
 import BubblePopupHelper from '../../../components/common/BubblePopupHelper';
+import EditIcon from '../../../components/icons-components/EditIcon';
 
 interface State {
   canSetWorkerCount: boolean;
@@ -121,7 +122,12 @@ export default class Workers extends React.PureComponent<{}, State> {
         {!loading &&
           canSetWorkerCount && (
             <Tooltip overlay={translate('background_tasks.change_number_of_workers')}>
-              <a className="icon-edit spacer-left" href="#" onClick={this.handleChangeClick} />
+              <a
+                className="js-edit link-no-underline spacer-left"
+                href="#"
+                onClick={this.handleChangeClick}>
+                <EditIcon className="text-text-top" />
+              </a>
             </Tooltip>
           )}
 
index 953e79d282ad5b56f55ceecfd3166365ecaa5094..cc07d0d4510c82894879beb042f56f1a15675a8e 100644 (file)
@@ -50,7 +50,7 @@ it('opens form', () => {
   });
   expect(wrapper).toMatchSnapshot();
 
-  click(wrapper.find('.icon-edit'));
+  click(wrapper.find('.js-edit'));
   expect(wrapper).toMatchSnapshot();
 });
 
index aabe0783b3db34269ce61df4b6d8f13b2467a26f..84a4e16e597ac914d7cb0fe6cf00c3c44ff524b1 100644 (file)
@@ -4,46 +4,23 @@ exports[`renders 1`] = `
 <td
   className="thin nowrap"
 >
-  <div
-    className="dropdown js-task-action"
+  <ActionsDropdown
+    className="js-task-action"
   >
-    <button
-      className="dropdown-toggle"
-      data-toggle="dropdown"
+    <ActionsDropdownItem
+      className="js-task-filter"
+      onClick={[Function]}
     >
-      <i
-        className="icon-dropdown"
-      />
-    </button>
-    <ul
-      className="dropdown-menu dropdown-menu-right"
+      background_tasks.filter_by_component_x.foo
+    </ActionsDropdownItem>
+    <ActionsDropdownItem
+      className="js-task-cancel"
+      destructive={true}
+      onClick={[Function]}
     >
-      <li>
-        <a
-          className="js-task-filter"
-          href="#"
-          onClick={[Function]}
-        >
-          <i
-            className="spacer-right icon-filter icon-gray"
-          />
-          background_tasks.filter_by_component_x.foo
-        </a>
-      </li>
-      <li>
-        <a
-          className="js-task-cancel"
-          href="#"
-          onClick={[Function]}
-        >
-          <i
-            className="spacer-right icon-delete"
-          />
-          background_tasks.cancel_task
-        </a>
-      </li>
-    </ul>
-  </div>
+      background_tasks.cancel_task
+    </ActionsDropdownItem>
+  </ActionsDropdown>
 </td>
 `;
 
@@ -51,34 +28,16 @@ exports[`renders 2`] = `
 <td
   className="thin nowrap"
 >
-  <div
-    className="dropdown js-task-action"
+  <ActionsDropdown
+    className="js-task-action"
   >
-    <button
-      className="dropdown-toggle"
-      data-toggle="dropdown"
+    <ActionsDropdownItem
+      className="js-task-filter"
+      onClick={[Function]}
     >
-      <i
-        className="icon-dropdown"
-      />
-    </button>
-    <ul
-      className="dropdown-menu dropdown-menu-right"
-    >
-      <li>
-        <a
-          className="js-task-filter"
-          href="#"
-          onClick={[Function]}
-        >
-          <i
-            className="spacer-right icon-filter icon-gray"
-          />
-          background_tasks.filter_by_component_x.foo
-        </a>
-      </li>
-    </ul>
-  </div>
+      background_tasks.filter_by_component_x.foo
+    </ActionsDropdownItem>
+  </ActionsDropdown>
 </td>
 `;
 
@@ -86,58 +45,29 @@ exports[`renders 3`] = `
 <td
   className="thin nowrap"
 >
-  <div
-    className="dropdown js-task-action"
+  <ActionsDropdown
+    className="js-task-action"
   >
-    <button
-      className="dropdown-toggle"
-      data-toggle="dropdown"
+    <ActionsDropdownItem
+      className="js-task-filter"
+      onClick={[Function]}
+    >
+      background_tasks.filter_by_component_x.foo
+    </ActionsDropdownItem>
+    <ActionsDropdownItem
+      className="js-task-cancel"
+      destructive={true}
+      onClick={[Function]}
     >
-      <i
-        className="icon-dropdown"
-      />
-    </button>
-    <ul
-      className="dropdown-menu dropdown-menu-right"
+      background_tasks.cancel_task
+    </ActionsDropdownItem>
+    <ActionsDropdownItem
+      className="js-task-show-scanner-context"
+      onClick={[Function]}
     >
-      <li>
-        <a
-          className="js-task-filter"
-          href="#"
-          onClick={[Function]}
-        >
-          <i
-            className="spacer-right icon-filter icon-gray"
-          />
-          background_tasks.filter_by_component_x.foo
-        </a>
-      </li>
-      <li>
-        <a
-          className="js-task-cancel"
-          href="#"
-          onClick={[Function]}
-        >
-          <i
-            className="spacer-right icon-delete"
-          />
-          background_tasks.cancel_task
-        </a>
-      </li>
-      <li>
-        <a
-          className="js-task-show-scanner-context"
-          href="#"
-          onClick={[Function]}
-        >
-          <i
-            className="spacer-right icon-list icon-gray"
-          />
-          background_tasks.show_scanner_context
-        </a>
-      </li>
-    </ul>
-  </div>
+      background_tasks.show_scanner_context
+    </ActionsDropdownItem>
+  </ActionsDropdown>
 </td>
 `;
 
@@ -145,58 +75,29 @@ exports[`renders 4`] = `
 <td
   className="thin nowrap"
 >
-  <div
-    className="dropdown js-task-action"
+  <ActionsDropdown
+    className="js-task-action"
   >
-    <button
-      className="dropdown-toggle"
-      data-toggle="dropdown"
+    <ActionsDropdownItem
+      className="js-task-filter"
+      onClick={[Function]}
     >
-      <i
-        className="icon-dropdown"
-      />
-    </button>
-    <ul
-      className="dropdown-menu dropdown-menu-right"
+      background_tasks.filter_by_component_x.foo
+    </ActionsDropdownItem>
+    <ActionsDropdownItem
+      className="js-task-cancel"
+      destructive={true}
+      onClick={[Function]}
     >
-      <li>
-        <a
-          className="js-task-filter"
-          href="#"
-          onClick={[Function]}
-        >
-          <i
-            className="spacer-right icon-filter icon-gray"
-          />
-          background_tasks.filter_by_component_x.foo
-        </a>
-      </li>
-      <li>
-        <a
-          className="js-task-cancel"
-          href="#"
-          onClick={[Function]}
-        >
-          <i
-            className="spacer-right icon-delete"
-          />
-          background_tasks.cancel_task
-        </a>
-      </li>
-      <li>
-        <a
-          className="js-task-show-stacktrace"
-          href="#"
-          onClick={[Function]}
-        >
-          <i
-            className="spacer-right icon-list icon-red"
-          />
-          background_tasks.show_stacktrace
-        </a>
-      </li>
-    </ul>
-  </div>
+      background_tasks.cancel_task
+    </ActionsDropdownItem>
+    <ActionsDropdownItem
+      className="js-task-show-stacktrace"
+      onClick={[Function]}
+    >
+      background_tasks.show_stacktrace
+    </ActionsDropdownItem>
+  </ActionsDropdown>
 </td>
 `;
 
@@ -204,34 +105,17 @@ exports[`renders 5`] = `
 <td
   className="thin nowrap"
 >
-  <div
-    className="dropdown js-task-action"
+  <ActionsDropdown
+    className="js-task-action"
   >
-    <button
-      className="dropdown-toggle"
-      data-toggle="dropdown"
-    >
-      <i
-        className="icon-dropdown"
-      />
-    </button>
-    <ul
-      className="dropdown-menu dropdown-menu-right"
+    <ActionsDropdownItem
+      className="js-task-cancel"
+      destructive={true}
+      onClick={[Function]}
     >
-      <li>
-        <a
-          className="js-task-cancel"
-          href="#"
-          onClick={[Function]}
-        >
-          <i
-            className="spacer-right icon-delete"
-          />
-          background_tasks.cancel_task
-        </a>
-      </li>
-    </ul>
-  </div>
+      background_tasks.cancel_task
+    </ActionsDropdownItem>
+  </ActionsDropdown>
 </td>
 `;
 
index d9d6b6fe2f2dedcff4014f980ebbd50f2b73b7f8..73abbbf04b49152a1a65740f0813a7cc1de672dc 100644 (file)
@@ -13,10 +13,14 @@ exports[`opens form 1`] = `
     placement="bottom"
   >
     <a
-      className="icon-edit spacer-left"
+      className="js-edit link-no-underline spacer-left"
       href="#"
       onClick={[Function]}
-    />
+    >
+      <EditIcon
+        className="text-text-top"
+      />
+    </a>
   </Tooltip>
 </div>
 `;
@@ -34,10 +38,14 @@ exports[`opens form 2`] = `
     placement="bottom"
   >
     <a
-      className="icon-edit spacer-left"
+      className="js-edit link-no-underline spacer-left"
       href="#"
       onClick={[Function]}
-    />
+    >
+      <EditIcon
+        className="text-text-top"
+      />
+    </a>
   </Tooltip>
   <WorkersForm
     onClose={[Function]}
@@ -68,10 +76,14 @@ exports[`renders 2`] = `
     placement="bottom"
   >
     <a
-      className="icon-edit spacer-left"
+      className="js-edit link-no-underline spacer-left"
       href="#"
       onClick={[Function]}
-    />
+    >
+      <EditIcon
+        className="text-text-top"
+      />
+    </a>
   </Tooltip>
 </div>
 `;
@@ -97,10 +109,14 @@ exports[`renders 3`] = `
     placement="bottom"
   >
     <a
-      className="icon-edit spacer-left"
+      className="js-edit link-no-underline spacer-left"
       href="#"
       onClick={[Function]}
-    />
+    >
+      <EditIcon
+        className="text-text-top"
+      />
+    </a>
   </Tooltip>
 </div>
 `;
@@ -157,10 +173,14 @@ exports[`updates worker count 1`] = `
     placement="bottom"
   >
     <a
-      className="icon-edit spacer-left"
+      className="js-edit link-no-underline spacer-left"
       href="#"
       onClick={[Function]}
-    />
+    >
+      <EditIcon
+        className="text-text-top"
+      />
+    </a>
   </Tooltip>
   <WorkersForm
     onClose={[Function]}
@@ -190,10 +210,14 @@ exports[`updates worker count 2`] = `
     placement="bottom"
   >
     <a
-      className="icon-edit spacer-left"
+      className="js-edit link-no-underline spacer-left"
       href="#"
       onClick={[Function]}
-    />
+    >
+      <EditIcon
+        className="text-text-top"
+      />
+    </a>
   </Tooltip>
 </div>
 `;
index 8efd57f4c110a54ac62a2c44e985916672fed075..0d40e51c4dbbb4a9a0864b6182f69c6d799c9a0d 100644 (file)
 </td>
 
 <td class="thin nowrap">
-  <a class="js-custom-measure-update icon-edit" title="Update" data-toggle="tooltip" href="#"></a>
-  <a class="js-custom-measure-delete icon-delete" title="Delete" data-toggle="tooltip" href="#"></a>
+  <div class="dropdown">
+    <button class="dropdown-toggle" data-toggle="dropdown">
+      {{settingsIcon}}<i class="icon-dropdown little-spacer-left" />
+    </button>
+    <ul class="dropdown-menu dropdown-menu-right">
+      <li>
+        <a class="js-custom-measure-update" href="#">{{t 'update_verb'}}</a>
+      </li>
+      <li class="divider" />
+      <li>
+        <a class="js-custom-measure-delete text-danger" href="#">{{t 'delete'}}</a>
+      </li>
+    </ul>
+  </div>
 </td>
index 9f6bab623214accd3b725fe3c857dadd5e2409ea..6d2600c9e410afb6822790043597f6685edf942c 100644 (file)
@@ -1,7 +1,19 @@
 <div class="pull-right big-spacer-left nowrap">
   {{#unless default}}
-    <a class="js-group-update icon-edit little-spacer-right" title="{{t 'update_details'}}" data-toggle="tooltip" href="#"></a>
-    <a class="js-group-delete icon-delete" title="{{t 'delete'}}" data-toggle="tooltip" href="#"></a>
+    <div class="dropdown">
+      <button class="dropdown-toggle" data-toggle="dropdown">
+        {{settingsIcon}}<i class="icon-dropdown little-spacer-left" />
+      </button>
+      <ul class="dropdown-menu dropdown-menu-right">
+        <li>
+          <a class="js-group-update" href="#">{{t 'update_details'}}</a>
+        </li>
+        <li class="divider" />
+        <li>
+          <a class="js-group-delete text-danger" href="#">{{t 'delete'}}</a>
+        </li>
+      </ul>
+    </div>
   {{/unless}}
 </div>
 
index 4f696e1d4914fff567058539f2d14ff8fb10a0fd..4033a0c9024f25268be09539064d4d451ed0f901 100644 (file)
@@ -1,6 +1,18 @@
 <div class="pull-right big-spacer-left nowrap">
-  <a class="js-metric-update icon-edit" title="{{t 'update_verb'}}" data-toggle="tooltip" href="#"></a>
-  <a class="js-metric-delete icon-delete" title="{{t 'delete'}}" data-toggle="tooltip" href="#"></a>
+  <div class="dropdown">
+    <button class="dropdown-toggle" data-toggle="dropdown">
+      {{settingsIcon}}<i class="icon-dropdown little-spacer-left" />
+    </button>
+    <ul class="dropdown-menu dropdown-menu-right">
+      <li>
+        <a class="js-metric-update" href="#">{{t 'update_verb'}}</a>
+      </li>
+      <li class="divider" />
+      <li>
+        <a class="js-metric-delete text-danger" href="#">{{t 'delete'}}</a>
+      </li>
+    </ul>
+  </div>
 </div>
 
 <div class="display-inline-block text-top width-30">
index 7c5c784dfd6396a9e1d19aa6892aa902cb615344..1147d0239be2c7b3f0f684d01bb43aad129abfe6 100644 (file)
@@ -24,7 +24,9 @@ import { translateWithParameters } from '../../../helpers/l10n';
 import { formatMeasure } from '../../../helpers/measures';
 import RemoveMemberForm from './forms/RemoveMemberForm';
 import ManageMemberGroupsForm from './forms/ManageMemberGroupsForm';
-import SettingsIcon from '../../../components/icons-components/SettingsIcon';
+import ActionsDropdown, {
+  ActionsDropdownDivider
+} from '../../../components/controls/ActionsDropdown';
 /*:: import type { Member } from '../../../store/organizationsMembers/actions'; */
 /*:: import type { Organization, OrgGroup } from '../../../store/organizations/duck'; */
 
@@ -64,31 +66,20 @@ export default class MembersListItem extends React.PureComponent {
         )}
         {organization.canAdmin && (
           <td className="nowrap text-middle text-right">
-            <div className="dropdown">
-              <button
-                className="dropdown-toggle little-spacer-right button-compact"
-                data-toggle="dropdown">
-                <SettingsIcon style={{ marginTop: 4 }} /> <i className="icon-dropdown" />
-              </button>
-              <ul className="dropdown-menu dropdown-menu-right">
-                <li>
-                  <ManageMemberGroupsForm
-                    organizationGroups={this.props.organizationGroups}
-                    organization={this.props.organization}
-                    updateMemberGroups={this.props.updateMemberGroups}
-                    member={this.props.member}
-                  />
-                </li>
-                <li role="separator" className="divider" />
-                <li>
-                  <RemoveMemberForm
-                    organization={this.props.organization}
-                    removeMember={this.props.removeMember}
-                    member={this.props.member}
-                  />
-                </li>
-              </ul>
-            </div>
+            <ActionsDropdown>
+              <ManageMemberGroupsForm
+                organizationGroups={this.props.organizationGroups}
+                organization={this.props.organization}
+                updateMemberGroups={this.props.updateMemberGroups}
+                member={this.props.member}
+              />
+              <ActionsDropdownDivider />
+              <RemoveMemberForm
+                organization={this.props.organization}
+                removeMember={this.props.removeMember}
+                member={this.props.member}
+              />
+            </ActionsDropdown>
           </td>
         )}
       </tr>
index 0bfde40983a1eadba42272e05632118f777dee96..b88cbc6b7397c5eb790dcb99e74be175dc5c69fb 100644 (file)
@@ -31,70 +31,41 @@ exports[`should groups at 0 if the groupCount field is not defined (just added u
   <td
     className="nowrap text-middle text-right"
   >
-    <div
-      className="dropdown"
-    >
-      <button
-        className="dropdown-toggle little-spacer-right button-compact"
-        data-toggle="dropdown"
-      >
-        <SettingsIcon
-          style={
-            Object {
-              "marginTop": 4,
-            }
+    <ActionsDropdown>
+      <ManageMemberGroupsForm
+        member={
+          Object {
+            "avatar": "7daf6c79d4802916d83f6266e24850af",
+            "login": "john",
+            "name": "John Doe",
+          }
+        }
+        organization={
+          Object {
+            "canAdmin": true,
+            "key": "foo",
+            "name": "Foo",
+          }
+        }
+      />
+      <ActionsDropdownDivider />
+      <RemoveMemberForm
+        member={
+          Object {
+            "avatar": "7daf6c79d4802916d83f6266e24850af",
+            "login": "john",
+            "name": "John Doe",
+          }
+        }
+        organization={
+          Object {
+            "canAdmin": true,
+            "key": "foo",
+            "name": "Foo",
           }
-        />
-         
-        <i
-          className="icon-dropdown"
-        />
-      </button>
-      <ul
-        className="dropdown-menu dropdown-menu-right"
-      >
-        <li>
-          <ManageMemberGroupsForm
-            member={
-              Object {
-                "avatar": "7daf6c79d4802916d83f6266e24850af",
-                "login": "john",
-                "name": "John Doe",
-              }
-            }
-            organization={
-              Object {
-                "canAdmin": true,
-                "key": "foo",
-                "name": "Foo",
-              }
-            }
-          />
-        </li>
-        <li
-          className="divider"
-          role="separator"
-        />
-        <li>
-          <RemoveMemberForm
-            member={
-              Object {
-                "avatar": "7daf6c79d4802916d83f6266e24850af",
-                "login": "john",
-                "name": "John Doe",
-              }
-            }
-            organization={
-              Object {
-                "canAdmin": true,
-                "key": "foo",
-                "name": "Foo",
-              }
-            }
-          />
-        </li>
-      </ul>
-    </div>
+        }
+      />
+    </ActionsDropdown>
   </td>
 </tr>
 `;
@@ -156,72 +127,43 @@ exports[`should render actions and groups for admin 1`] = `
   <td
     className="nowrap text-middle text-right"
   >
-    <div
-      className="dropdown"
-    >
-      <button
-        className="dropdown-toggle little-spacer-right button-compact"
-        data-toggle="dropdown"
-      >
-        <SettingsIcon
-          style={
-            Object {
-              "marginTop": 4,
-            }
+    <ActionsDropdown>
+      <ManageMemberGroupsForm
+        member={
+          Object {
+            "avatar": "",
+            "groupCount": 3,
+            "login": "admin",
+            "name": "Admin Istrator",
+          }
+        }
+        organization={
+          Object {
+            "canAdmin": true,
+            "key": "foo",
+            "name": "Foo",
+          }
+        }
+      />
+      <ActionsDropdownDivider />
+      <RemoveMemberForm
+        member={
+          Object {
+            "avatar": "",
+            "groupCount": 3,
+            "login": "admin",
+            "name": "Admin Istrator",
+          }
+        }
+        organization={
+          Object {
+            "canAdmin": true,
+            "key": "foo",
+            "name": "Foo",
           }
-        />
-         
-        <i
-          className="icon-dropdown"
-        />
-      </button>
-      <ul
-        className="dropdown-menu dropdown-menu-right"
-      >
-        <li>
-          <ManageMemberGroupsForm
-            member={
-              Object {
-                "avatar": "",
-                "groupCount": 3,
-                "login": "admin",
-                "name": "Admin Istrator",
-              }
-            }
-            organization={
-              Object {
-                "canAdmin": true,
-                "key": "foo",
-                "name": "Foo",
-              }
-            }
-          />
-        </li>
-        <li
-          className="divider"
-          role="separator"
-        />
-        <li>
-          <RemoveMemberForm
-            member={
-              Object {
-                "avatar": "",
-                "groupCount": 3,
-                "login": "admin",
-                "name": "Admin Istrator",
-              }
-            }
-            organization={
-              Object {
-                "canAdmin": true,
-                "key": "foo",
-                "name": "Foo",
-              }
-            }
-          />
-        </li>
-      </ul>
-    </div>
+        }
+      />
+    </ActionsDropdown>
   </td>
 </tr>
 `;
index 6e8818b6e85a7d8fa982056f129bb359a891f9a5..58f6838bc5df6728b3c5fbefb4633442c5adf147 100644 (file)
@@ -24,6 +24,7 @@ import { getUserGroups } from '../../../../api/users';
 import Modal from '../../../../components/controls/Modal';
 import { translate, translateWithParameters } from '../../../../helpers/l10n';
 import OrganizationGroupCheckbox from '../OrganizationGroupCheckbox';
+import { ActionsDropdownItem } from '../../../../components/controls/ActionsDropdown';
 /*:: import type { Member } from '../../../../store/organizationsMembers/actions'; */
 /*:: import type { Organization, OrgGroup } from '../../../../store/organizations/duck'; */
 
@@ -45,14 +46,22 @@ type State = {
 */
 
 export default class ManageMemberGroupsForm extends React.PureComponent {
+  /*:: mounted: boolean */
   /*:: props: Props; */
 
   state /*: State */ = {
     open: false
   };
 
-  openForm = (evt /*: MouseEvent */) => {
-    evt.preventDefault();
+  componentDidMount() {
+    this.mounted = true;
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  openForm = () => {
     this.loadUserGroups();
     this.setState({ open: true });
   };
@@ -63,9 +72,18 @@ export default class ManageMemberGroupsForm extends React.PureComponent {
 
   loadUserGroups = () => {
     this.setState({ loading: true });
-    getUserGroups(this.props.member.login, this.props.organization.key).then(response => {
-      this.setState({ loading: false, userGroups: keyBy(response.groups, 'name') });
-    });
+    getUserGroups(this.props.member.login, this.props.organization.key).then(
+      response => {
+        if (this.mounted) {
+          this.setState({ loading: false, userGroups: keyBy(response.groups, 'name') });
+        }
+      },
+      () => {
+        if (this.mounted) {
+          this.setState({ loading: false });
+        }
+      }
+    );
   };
 
   isGroupSelected = (groupName /*: string */) => {
@@ -148,9 +166,9 @@ export default class ManageMemberGroupsForm extends React.PureComponent {
 
   render() {
     const buttonComponent = (
-      <a key="manage-member-button" onClick={this.openForm} href="#">
+      <ActionsDropdownItem onClick={this.openForm}>
         {translate('organization.members.manage_groups')}
-      </a>
+      </ActionsDropdownItem>
     );
     if (this.state.open) {
       return [buttonComponent, this.renderModal()];
index 94fb72dd24f6f0aca2efc17aec722ddf8da697ae..9a0db75be63a3fc3493335256388b3ecc6f6c11b 100644 (file)
@@ -20,6 +20,7 @@
 // @flow
 import React from 'react';
 import Modal from '../../../../components/controls/Modal';
+import { ActionsDropdownItem } from '../../../../components/controls/ActionsDropdown';
 import { translate, translateWithParameters } from '../../../../helpers/l10n';
 /*:: import type { Member } from '../../../../store/organizationsMembers/actions'; */
 /*:: import type { Organization } from '../../../../store/organizations/duck'; */
@@ -45,8 +46,7 @@ export default class RemoveMemberForm extends React.PureComponent {
     open: false
   };
 
-  openForm = (evt /*: MouseEvent */) => {
-    evt.preventDefault();
+  openForm = () => {
     this.setState({ open: true });
   };
 
@@ -94,9 +94,9 @@ export default class RemoveMemberForm extends React.PureComponent {
 
   render() {
     const buttonComponent = (
-      <a key="remove-member-button" onClick={this.openForm} href="#">
+      <ActionsDropdownItem destructive={true} onClick={this.openForm}>
         {translate('organization.members.remove')}
-      </a>
+      </ActionsDropdownItem>
     );
     if (this.state.open) {
       return [buttonComponent, this.renderModal()];
index c28b5cbcd9ae274f57276e6cc75e232312185812..63503e40918584637549776d5e01f7bad16a0d37 100644 (file)
@@ -82,7 +82,7 @@ it('should render and open the modal', () => {
 
 it('should correctly handle user interactions', () => {
   const form = getMountedForm();
-  click(form.wrapper.find('a'));
+  form.wrapper.find('ActionsDropdownItem').prop('onClick')();
   expect(form.wrapper.state('open')).toBeTruthy();
   expect(form.instance.loadUserGroups).toBeCalled();
   expect(form.wrapper.state()).toMatchSnapshot();
index 5c007ae412310746d1fec768736e3bab1d283cfd..0fee4e72b587c4d425f9870983fe44766d870b30 100644 (file)
@@ -42,7 +42,7 @@ it('should correctly handle user interactions', () => {
     <RemoveMemberForm member={member} removeMember={removeMember} organization={organization} />
   );
   const instance = wrapper.instance();
-  click(wrapper.find('a'));
+  wrapper.find('ActionsDropdownItem').prop('onClick')();
   expect(wrapper.state('open')).toBeTruthy();
   instance.handleSubmit(mockEvent);
   expect(removeMember.mock.calls).toMatchSnapshot();
index 51d9f708ee7354d6a5d405fb7c45a0cd89e94405..94ff9bb670542cbdca377a768110436e835ff73c 100644 (file)
@@ -69,23 +69,20 @@ Object {
 `;
 
 exports[`should render and open the modal 1`] = `
-<a
-  href="#"
-  key="manage-member-button"
+<ActionsDropdownItem
   onClick={[Function]}
 >
   organization.members.manage_groups
-</a>
+</ActionsDropdownItem>
 `;
 
 exports[`should render and open the modal 2`] = `
 Array [
-  <a
-    href="#"
+  <ActionsDropdownItem
     onClick={[Function]}
   >
     organization.members.manage_groups
-  </a>,
+  </ActionsDropdownItem>,
   <Modal
     contentLabel="organization.members.manage_groups"
     onRequestClose={[Function]}
index 3e362fcf4dbf5b325dcb4776493df892e962c376..861e8d98f0216dc65f0121edaf657b3d24834a48 100644 (file)
@@ -14,23 +14,22 @@ Array [
 `;
 
 exports[`should render and open the modal 1`] = `
-<a
-  href="#"
-  key="remove-member-button"
+<ActionsDropdownItem
+  destructive={true}
   onClick={[Function]}
 >
   organization.members.remove
-</a>
+</ActionsDropdownItem>
 `;
 
 exports[`should render and open the modal 2`] = `
 Array [
-  <a
-    href="#"
+  <ActionsDropdownItem
+    destructive={true}
     onClick={[Function]}
   >
     organization.members.remove
-  </a>,
+  </ActionsDropdownItem>,
   <Modal
     contentLabel="users.remove"
     onRequestClose={[Function]}
index 1c2e6f0618b655eef2c78f9843ef398bc5b3eb32..e0a644ff495ad4c123a991caad7e5d75f8a09f3f 100644 (file)
@@ -23,6 +23,7 @@ import { Link } from 'react-router';
 import { difference } from 'lodash';
 import Backbone from 'backbone';
 import { PermissionTemplateType, CallbackType } from '../propTypes';
+import ActionsDropdown, { ActionsDropdownItem } from '../../../components/controls/ActionsDropdown';
 import QualifierIcon from '../../../components/shared/QualifierIcon';
 import UpdateView from '../views/UpdateView';
 import DeleteView from '../views/DeleteView';
@@ -46,16 +47,14 @@ export default class ActionsCell extends React.PureComponent {
     router: PropTypes.object
   };
 
-  handleUpdateClick(e) {
-    e.preventDefault();
+  handleUpdateClick = () => {
     new UpdateView({
       model: new Backbone.Model(this.props.permissionTemplate),
       refresh: this.props.refresh
     }).render();
-  }
+  };
 
-  handleDeleteClick(e) {
-    e.preventDefault();
+  handleDeleteClick = () => {
     new DeleteView({
       model: new Backbone.Model(this.props.permissionTemplate)
     })
@@ -67,14 +66,14 @@ export default class ActionsCell extends React.PureComponent {
         this.props.refresh();
       })
       .render();
-  }
+  };
 
-  setDefault(qualifier, e) {
-    e.preventDefault();
+  setDefault = qualifier => () => {
     setDefaultPermissionTemplate(this.props.permissionTemplate.id, qualifier).then(
-      this.props.refresh
+      this.props.refresh,
+      () => {}
     );
-  }
+  };
 
   getAvailableQualifiers() {
     const topQualifiers =
@@ -84,16 +83,6 @@ export default class ActionsCell extends React.PureComponent {
     return difference(topQualifiers, this.props.permissionTemplate.defaultFor);
   }
 
-  renderDropdownIcon(icon) {
-    const style = {
-      display: 'inline-block',
-      width: 16,
-      marginRight: 4,
-      textAlign: 'center'
-    };
-    return <div style={style}>{icon}</div>;
-  }
-
   renderSetDefaultsControl() {
     const availableQualifiers = this.getAvailableQualifiers();
 
@@ -108,16 +97,13 @@ export default class ActionsCell extends React.PureComponent {
 
   renderSetDefaultLink(qualifier, child) {
     return (
-      <li key={qualifier}>
-        <a
-          href="#"
-          className="js-set-default"
-          data-qualifier={qualifier}
-          onClick={this.setDefault.bind(this, qualifier)}>
-          {this.renderDropdownIcon(<i className="icon-check" />)}
-          {child}
-        </a>
-      </li>
+      <ActionsDropdownItem
+        key={qualifier}
+        className="js-set-default"
+        data-qualifier={qualifier}
+        onClick={this.setDefault(qualifier)}>
+        {child}
+      </ActionsDropdownItem>
     );
   }
 
@@ -150,40 +136,25 @@ export default class ActionsCell extends React.PureComponent {
       : '/permission_templates';
 
     return (
-      <div className="dropdown">
-        <button className="dropdown-toggle" data-toggle="dropdown">
-          {translate('actions')} <i className="icon-dropdown" />
-        </button>
-
-        <ul className="dropdown-menu dropdown-menu-right">
-          {this.renderSetDefaultsControl()}
-
-          {!this.props.fromDetails && (
-            <li>
-              <Link to={{ pathname, query: { id: t.id } }}>
-                {this.renderDropdownIcon(<i className="icon-edit" />)}
-                {translate('edit_permissions')}
-              </Link>
-            </li>
-          )}
-
-          <li>
-            <a href="#" className="js-update" onClick={this.handleUpdateClick.bind(this)}>
-              {this.renderDropdownIcon(<i className="icon-edit" />)}
-              {translate('update_details')}
-            </a>
-          </li>
-
-          {t.defaultFor.length === 0 && (
-            <li>
-              <a href="#" className="js-delete" onClick={this.handleDeleteClick.bind(this)}>
-                {this.renderDropdownIcon(<i className="icon-delete" />)}
-                {translate('delete')}
-              </a>
-            </li>
-          )}
-        </ul>
-      </div>
+      <ActionsDropdown>
+        {this.renderSetDefaultsControl()}
+
+        {!this.props.fromDetails && (
+          <ActionsDropdownItem to={{ pathname, query: { id: t.id } }}>
+            {translate('edit_permissions')}
+          </ActionsDropdownItem>
+        )}
+
+        <ActionsDropdownItem className="js-update" onClick={this.handleUpdateClick}>
+          {translate('update_details')}
+        </ActionsDropdownItem>
+
+        {t.defaultFor.length === 0 && (
+          <ActionsDropdownItem className="js-delete" onClick={this.handleDeleteClick}>
+            {translate('delete')}
+          </ActionsDropdownItem>
+        )}
+      </ActionsDropdown>
     );
   }
 }
index 517e60cc26f6b37f2d3941942b6b7ba2b413224b..1f9453b96307e111b9f0eaeb1c77f58b0d881124 100644 (file)
@@ -24,7 +24,9 @@ import Events from './Events';
 import AddEventForm from './forms/AddEventForm';
 import RemoveAnalysisForm from './forms/RemoveAnalysisForm';
 import TimeTooltipFormatter from '../../../components/intl/TimeTooltipFormatter';
-import SettingsIcon from '../../../components/icons-components/SettingsIcon';
+import ActionsDropdown, {
+  ActionsDropdownDivider
+} from '../../../components/controls/ActionsDropdown';
 import { translate } from '../../../helpers/l10n';
 /*:: import type { Analysis } from '../types'; */
 
@@ -78,44 +80,29 @@ export default class ProjectActivityAnalysis extends React.PureComponent {
 
         {(canAddVersion || canAddEvent || canDeleteAnalyses) && (
           <div className="project-activity-analysis-actions spacer-left">
-            <div className="dropdown display-inline-block">
-              <button
-                className="js-analysis-actions button-small button-compact dropdown-toggle"
-                data-toggle="dropdown"
-                onClick={this.stopPropagation}>
-                <SettingsIcon size={12} style={{ marginTop: 3 }} /> <i className="icon-dropdown" />
-              </button>
-              <ul className="dropdown-menu dropdown-menu-right">
-                {canAddVersion && (
-                  <li>
-                    <AddEventForm
-                      addEvent={this.props.addVersion}
-                      analysis={analysis}
-                      addEventButtonText="project_activity.add_version"
-                    />
-                  </li>
-                )}
-                {canAddEvent && (
-                  <li>
-                    <AddEventForm
-                      addEvent={this.props.addCustomEvent}
-                      analysis={analysis}
-                      addEventButtonText="project_activity.add_custom_event"
-                    />
-                  </li>
-                )}
-                {(canAddVersion || canAddEvent) &&
-                  canDeleteAnalyses && <li role="separator" className="divider" />}
-                {canDeleteAnalyses && (
-                  <li>
-                    <RemoveAnalysisForm
-                      analysis={analysis}
-                      deleteAnalysis={this.props.deleteAnalysis}
-                    />
-                  </li>
-                )}
-              </ul>
-            </div>
+            <ActionsDropdown small={true} toggleClassName="js-analysis-actions">
+              {canAddVersion && (
+                <AddEventForm
+                  addEvent={this.props.addVersion}
+                  analysis={analysis}
+                  addEventButtonText="project_activity.add_version"
+                />
+              )}
+              {canAddEvent && (
+                <AddEventForm
+                  addEvent={this.props.addCustomEvent}
+                  analysis={analysis}
+                  addEventButtonText="project_activity.add_custom_event"
+                />
+              )}
+              {(canAddVersion || canAddEvent) && canDeleteAnalyses && <ActionsDropdownDivider />}
+              {canDeleteAnalyses && (
+                <RemoveAnalysisForm
+                  analysis={analysis}
+                  deleteAnalysis={this.props.deleteAnalysis}
+                />
+              )}
+            </ActionsDropdown>
           </div>
         )}
 
index 520a159d0c04a96551d0d39951eb0dad29b9edc1..50ff6ca16800730f655d6355bcf3a53758693b2d 100644 (file)
@@ -20,6 +20,7 @@
 // @flow
 import React from 'react';
 import Modal from '../../../../components/controls/Modal';
+import { ActionsDropdownItem } from '../../../../components/controls/ActionsDropdown';
 import { translate } from '../../../../helpers/l10n';
 /*:: import type { Analysis } from '../../types'; */
 
@@ -56,12 +57,8 @@ export default class AddEventForm extends React.PureComponent {
     this.mounted = false;
   }
 
-  openForm = (e /*: Event */) => {
-    e.preventDefault();
-    e.stopPropagation();
-    if (this.mounted) {
-      this.setState({ open: true });
-    }
+  openForm = () => {
+    this.setState({ open: true });
   };
 
   closeForm = () => {
@@ -137,9 +134,9 @@ export default class AddEventForm extends React.PureComponent {
 
   render() {
     const linkComponent = (
-      <a key="add-event-link" className="js-add-event" href="#" onClick={this.openForm}>
+      <ActionsDropdownItem className="js-add-event" onClick={this.openForm}>
         {translate(this.props.addEventButtonText)}
-      </a>
+      </ActionsDropdownItem>
     );
     if (this.state.open) {
       return [linkComponent, this.renderModal()];
index b3395d92a720c58f9bd79f15a9845a422d8f9753..1203ace800ce1512e92a9fd2be320f80a793ccab 100644 (file)
@@ -20,6 +20,7 @@
 // @flow
 import React from 'react';
 import Modal from '../../../../components/controls/Modal';
+import { ActionsDropdownItem } from '../../../../components/controls/ActionsDropdown';
 import { translate } from '../../../../helpers/l10n';
 /*:: import type { Analysis } from '../../types'; */
 
@@ -53,12 +54,8 @@ export default class RemoveAnalysisForm extends React.PureComponent {
     this.mounted = false;
   }
 
-  openForm = (e /*: Event */) => {
-    e.preventDefault();
-    e.stopPropagation();
-    if (this.mounted) {
-      this.setState({ open: true });
-    }
+  openForm = () => {
+    this.setState({ open: true });
   };
 
   closeForm = () => {
@@ -119,9 +116,12 @@ export default class RemoveAnalysisForm extends React.PureComponent {
 
   render() {
     const linkComponent = (
-      <a key="delete-analysis-link" className="js-delete-analysis" href="#" onClick={this.openForm}>
+      <ActionsDropdownItem
+        className="js-delete-analysis"
+        destructive={true}
+        onClick={this.openForm}>
         {translate('project_activity.delete_analysis')}
-      </a>
+      </ActionsDropdownItem>
     );
     if (this.state.open) {
       return [linkComponent, this.renderModal()];
index 5290f17718cc492cce9b7fd1111aa633f0a44db4..608c98e27b6fa94cf4ccb2268bb0dedfa229aa5b 100644 (file)
@@ -28,7 +28,10 @@ import { isShortLivingBranch, isLongLivingBranch } from '../../../helpers/branch
 import { translate } from '../../../helpers/l10n';
 import RenameBranchModal from './RenameBranchModal';
 import DateFromNow from '../../../components/intl/DateFromNow';
-import SettingsIcon from '../../../components/icons-components/SettingsIcon';
+import ActionsDropdown, {
+  ActionsDropdownItem,
+  ActionsDropdownDivider
+} from '../../../components/controls/ActionsDropdown';
 
 interface Props {
   branch: Branch;
@@ -54,9 +57,7 @@ export default class BranchRow extends React.PureComponent<Props, State> {
     this.mounted = false;
   }
 
-  handleDeleteClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
-    event.preventDefault();
-    event.currentTarget.blur();
+  handleDeleteClick = () => {
     this.setState({ deleting: true });
   };
 
@@ -64,9 +65,7 @@ export default class BranchRow extends React.PureComponent<Props, State> {
     this.setState({ deleting: false });
   };
 
-  handleRenameClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
-    event.preventDefault();
-    event.currentTarget.blur();
+  handleRenameClick = () => {
     this.setState({ renaming: true });
   };
 
@@ -81,9 +80,7 @@ export default class BranchRow extends React.PureComponent<Props, State> {
     this.setState({ renaming: false });
   };
 
-  handleChangeLeakClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
-    event.preventDefault();
-    event.currentTarget.blur();
+  handleChangeLeakClick = () => {
     this.setState({ changingLeak: true });
   };
 
@@ -93,69 +90,32 @@ export default class BranchRow extends React.PureComponent<Props, State> {
     }
   };
 
-  render() {
+  renderActions() {
     const { branch, component } = this.props;
-
     return (
-      <tr>
-        <td>
-          <BranchIcon
-            branch={branch}
-            className={classNames('little-spacer-right', {
-              'big-spacer-left': isShortLivingBranch(branch) && !branch.isOrphan
-            })}
-          />
-          {branch.name}
-          {branch.isMain && (
-            <div className="outline-badge spacer-left">{translate('branches.main_branch')}</div>
+      <td className="thin nowrap text-right">
+        <ActionsDropdown className="ig-spacer-left">
+          {isLongLivingBranch(branch) && (
+            <ActionsDropdownItem
+              className="js-change-leak-period"
+              onClick={this.handleChangeLeakClick}>
+              {translate('branches.set_leak_period')}
+            </ActionsDropdownItem>
           )}
-        </td>
-        <td className="thin nowrap text-right">
-          <BranchStatus branch={branch} />
-        </td>
-        <td className="thin nowrap text-right">
-          {branch.analysisDate && <DateFromNow date={branch.analysisDate} />}
-        </td>
-        <td className="thin nowrap text-right">
-          <div className="dropdown big-spacer-left">
-            <button
-              className="dropdown-toggle little-spacer-right button-compact"
-              data-toggle="dropdown">
-              <SettingsIcon style={{ marginTop: 4 }} /> <i className="icon-dropdown" />
-            </button>
-            <ul className="dropdown-menu dropdown-menu-right">
-              {isLongLivingBranch(branch) && (
-                <li>
-                  <a
-                    className="js-change-leak-period link-no-underline"
-                    href="#"
-                    onClick={this.handleChangeLeakClick}>
-                    {translate('branches.set_leak_period')}
-                  </a>
-                </li>
-              )}
-              {branch.isMain ? (
-                <li>
-                  <a
-                    className="js-rename link-no-underline"
-                    href="#"
-                    onClick={this.handleRenameClick}>
-                    {translate('branches.rename')}
-                  </a>
-                </li>
-              ) : (
-                <li>
-                  <a
-                    className="js-delete link-no-underline"
-                    href="#"
-                    onClick={this.handleDeleteClick}>
-                    {translate('branches.delete')}
-                  </a>
-                </li>
-              )}
-            </ul>
-          </div>
-        </td>
+          {isLongLivingBranch(branch) && !branch.isMain && <ActionsDropdownDivider />}
+          {branch.isMain ? (
+            <ActionsDropdownItem className="js-rename" onClick={this.handleRenameClick}>
+              {translate('branches.rename')}
+            </ActionsDropdownItem>
+          ) : (
+            <ActionsDropdownItem
+              className="js-delete"
+              destructive={true}
+              onClick={this.handleDeleteClick}>
+              {translate('branches.delete')}
+            </ActionsDropdownItem>
+          )}
+        </ActionsDropdown>
 
         {this.state.deleting && (
           <DeleteBranchModal
@@ -182,6 +142,34 @@ export default class BranchRow extends React.PureComponent<Props, State> {
             project={component}
           />
         )}
+      </td>
+    );
+  }
+
+  render() {
+    const { branch } = this.props;
+
+    return (
+      <tr>
+        <td>
+          <BranchIcon
+            branch={branch}
+            className={classNames('little-spacer-right', {
+              'big-spacer-left': isShortLivingBranch(branch) && !branch.isOrphan
+            })}
+          />
+          {branch.name}
+          {branch.isMain && (
+            <div className="outline-badge spacer-left">{translate('branches.main_branch')}</div>
+          )}
+        </td>
+        <td className="thin nowrap text-right">
+          <BranchStatus branch={branch} />
+        </td>
+        <td className="thin nowrap text-right">
+          {branch.analysisDate && <DateFromNow date={branch.analysisDate} />}
+        </td>
+        {this.renderActions()}
       </tr>
     );
   }
index febb99812b8eb45b6b92e503194e0b254f5a8dfe..5a28f352855c32a9e7886aea2e80cae39704e7cd 100644 (file)
@@ -37,39 +37,16 @@ exports[`renders main branch 1`] = `
   <td
     className="thin nowrap text-right"
   >
-    <div
-      className="dropdown big-spacer-left"
+    <ActionsDropdown
+      className="ig-spacer-left"
     >
-      <button
-        className="dropdown-toggle little-spacer-right button-compact"
-        data-toggle="dropdown"
-      >
-        <SettingsIcon
-          style={
-            Object {
-              "marginTop": 4,
-            }
-          }
-        />
-         
-        <i
-          className="icon-dropdown"
-        />
-      </button>
-      <ul
-        className="dropdown-menu dropdown-menu-right"
+      <ActionsDropdownItem
+        className="js-rename"
+        onClick={[Function]}
       >
-        <li>
-          <a
-            className="js-rename link-no-underline"
-            href="#"
-            onClick={[Function]}
-          >
-            branches.rename
-          </a>
-        </li>
-      </ul>
-    </div>
+        branches.rename
+      </ActionsDropdownItem>
+    </ActionsDropdown>
   </td>
 </tr>
 `;
@@ -116,39 +93,17 @@ exports[`renders short-living branch 1`] = `
   <td
     className="thin nowrap text-right"
   >
-    <div
-      className="dropdown big-spacer-left"
+    <ActionsDropdown
+      className="ig-spacer-left"
     >
-      <button
-        className="dropdown-toggle little-spacer-right button-compact"
-        data-toggle="dropdown"
-      >
-        <SettingsIcon
-          style={
-            Object {
-              "marginTop": 4,
-            }
-          }
-        />
-         
-        <i
-          className="icon-dropdown"
-        />
-      </button>
-      <ul
-        className="dropdown-menu dropdown-menu-right"
+      <ActionsDropdownItem
+        className="js-delete"
+        destructive={true}
+        onClick={[Function]}
       >
-        <li>
-          <a
-            className="js-delete link-no-underline"
-            href="#"
-            onClick={[Function]}
-          >
-            branches.delete
-          </a>
-        </li>
-      </ul>
-    </div>
+        branches.delete
+      </ActionsDropdownItem>
+    </ActionsDropdown>
   </td>
 </tr>
 `;
index f3ea88491a26c4fde3523a392d658d38d428206d..5dd7604092dda5be6d1521256c7e88fecd960faa 100644 (file)
@@ -21,6 +21,7 @@ import * as React from 'react';
 import ChangeVisibilityForm from './ChangeVisibilityForm';
 import { Organization, Visibility } from '../../app/types';
 import { translate } from '../../helpers/l10n';
+import EditIcon from '../../components/icons-components/EditIcon';
 
 export interface Props {
   hasProvisionPermission?: boolean;
@@ -62,10 +63,11 @@ export default class Header extends React.PureComponent<Props, State> {
             {translate('organization.default_visibility_of_new_projects')}{' '}
             <strong>{translate('visibility', organization.projectVisibility)}</strong>
             <a
-              className="js-change-visibility spacer-left icon-edit"
+              className="js-change-visibility spacer-left link-no-underline"
               href="#"
-              onClick={this.handleChangeVisibilityClick}
-            />
+              onClick={this.handleChangeVisibilityClick}>
+              <EditIcon className="text-text-top" />
+            </a>
           </span>
           {this.props.hasProvisionPermission && (
             <button id="create-project" onClick={this.handleCreateProjectClick}>
index ce7df2a1c43a029f438ec69c5b8da5c9fdb2ccd4..2f3693f0630105f52fe6fd7c3b5785c3f96d447a 100644 (file)
@@ -27,6 +27,7 @@ import QualifierIcon from '../../components/shared/QualifierIcon';
 import { translate } from '../../helpers/l10n';
 import { getComponentPermissionsUrl } from '../../helpers/urls';
 import DateTooltipFormatter from '../../components/intl/DateTooltipFormatter';
+import ActionsDropdown, { ActionsDropdownItem } from '../../components/controls/ActionsDropdown';
 
 interface Props {
   onApplyTemplateClick: (project: Project) => void;
@@ -40,9 +41,7 @@ export default class ProjectRow extends React.PureComponent<Props> {
     this.props.onProjectCheck(this.props.project, checked);
   };
 
-  handleApplyTemplateClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
-    event.preventDefault();
-    event.currentTarget.blur();
+  handleApplyTemplateClick = () => {
     this.props.onApplyTemplateClick(this.props.project);
   };
 
@@ -82,23 +81,16 @@ export default class ProjectRow extends React.PureComponent<Props> {
         </td>
 
         <td className="thin nowrap">
-          <div className="dropdown">
-            <button className="dropdown-toggle" data-toggle="dropdown">
-              {translate('actions')} <i className="icon-dropdown" />
-            </button>
-            <ul className="dropdown-menu dropdown-menu-right">
-              <li>
-                <Link to={getComponentPermissionsUrl(project.key)}>
-                  {translate('edit_permissions')}
-                </Link>
-              </li>
-              <li>
-                <a className="js-apply-template" href="#" onClick={this.handleApplyTemplateClick}>
-                  {translate('projects_role.apply_template')}
-                </a>
-              </li>
-            </ul>
-          </div>
+          <ActionsDropdown>
+            <ActionsDropdownItem to={getComponentPermissionsUrl(project.key)}>
+              {translate('edit_permissions')}
+            </ActionsDropdownItem>
+            <ActionsDropdownItem
+              className="js-apply-template"
+              onClick={this.handleApplyTemplateClick}>
+              {translate('projects_role.apply_template')}
+            </ActionsDropdownItem>
+          </ActionsDropdown>
         </td>
       </tr>
     );
index 9fb16cfbed5a0c9963877267cdc17364f9ce8075..c94a5a5cb29d3435b95d01ad47a23c0b964ea039 100644 (file)
@@ -35,10 +35,14 @@ exports[`renders 1`] = `
         visibility.public
       </strong>
       <a
-        className="js-change-visibility spacer-left icon-edit"
+        className="js-change-visibility spacer-left link-no-underline"
         href="#"
         onClick={[Function]}
-      />
+      >
+        <EditIcon
+          className="text-text-top"
+        />
+      </a>
     </span>
     <button
       id="create-project"
index 87d61db023e53234d6f4ccbd4417bdedb9561af0..a76855dee4465208500ee6c1f2003822335a2eed 100644 (file)
@@ -64,49 +64,26 @@ exports[`renders 1`] = `
   <td
     className="thin nowrap"
   >
-    <div
-      className="dropdown"
-    >
-      <button
-        className="dropdown-toggle"
-        data-toggle="dropdown"
+    <ActionsDropdown>
+      <ActionsDropdownItem
+        to={
+          Object {
+            "pathname": "/project_roles",
+            "query": Object {
+              "id": "project",
+            },
+          }
+        }
       >
-        actions
-         
-        <i
-          className="icon-dropdown"
-        />
-      </button>
-      <ul
-        className="dropdown-menu dropdown-menu-right"
+        edit_permissions
+      </ActionsDropdownItem>
+      <ActionsDropdownItem
+        className="js-apply-template"
+        onClick={[Function]}
       >
-        <li>
-          <Link
-            onlyActiveOnIndex={false}
-            style={Object {}}
-            to={
-              Object {
-                "pathname": "/project_roles",
-                "query": Object {
-                  "id": "project",
-                },
-              }
-            }
-          >
-            edit_permissions
-          </Link>
-        </li>
-        <li>
-          <a
-            className="js-apply-template"
-            href="#"
-            onClick={[Function]}
-          >
-            projects_role.apply_template
-          </a>
-        </li>
-      </ul>
-    </div>
+        projects_role.apply_template
+      </ActionsDropdownItem>
+    </ActionsDropdown>
   </td>
 </tr>
 `;
@@ -173,49 +150,26 @@ exports[`renders 2`] = `
   <td
     className="thin nowrap"
   >
-    <div
-      className="dropdown"
-    >
-      <button
-        className="dropdown-toggle"
-        data-toggle="dropdown"
+    <ActionsDropdown>
+      <ActionsDropdownItem
+        to={
+          Object {
+            "pathname": "/project_roles",
+            "query": Object {
+              "id": "project",
+            },
+          }
+        }
       >
-        actions
-         
-        <i
-          className="icon-dropdown"
-        />
-      </button>
-      <ul
-        className="dropdown-menu dropdown-menu-right"
+        edit_permissions
+      </ActionsDropdownItem>
+      <ActionsDropdownItem
+        className="js-apply-template"
+        onClick={[Function]}
       >
-        <li>
-          <Link
-            onlyActiveOnIndex={false}
-            style={Object {}}
-            to={
-              Object {
-                "pathname": "/project_roles",
-                "query": Object {
-                  "id": "project",
-                },
-              }
-            }
-          >
-            edit_permissions
-          </Link>
-        </li>
-        <li>
-          <a
-            className="js-apply-template"
-            href="#"
-            onClick={[Function]}
-          >
-            projects_role.apply_template
-          </a>
-        </li>
-      </ul>
-    </div>
+        projects_role.apply_template
+      </ActionsDropdownItem>
+    </ActionsDropdown>
   </td>
 </tr>
 `;
index 8906db09a3b4468d7e1b46efaa1af375bea8d960..0a2aff5cffbe8f5f17057d5849138b8338f3182f 100644 (file)
@@ -19,7 +19,6 @@
  */
 import * as React from 'react';
 import * as PropTypes from 'prop-types';
-import { Link } from 'react-router';
 import RenameProfileForm from './RenameProfileForm';
 import CopyProfileForm from './CopyProfileForm';
 import DeleteProfileForm from './DeleteProfileForm';
@@ -28,8 +27,13 @@ import { getRulesUrl } from '../../../helpers/urls';
 import { setDefaultProfile } from '../../../api/quality-profiles';
 import { getProfilePath, getProfileComparePath, getProfilesPath } from '../utils';
 import { Profile } from '../types';
+import ActionsDropdown, {
+  ActionsDropdownItem,
+  ActionsDropdownDivider
+} from '../../../components/controls/ActionsDropdown';
 
 interface Props {
+  className?: string;
   fromList?: boolean;
   onRequestFail: (reasong: any) => void;
   organization: string | null;
@@ -57,50 +61,52 @@ export default class ProfileActions extends React.PureComponent<Props, State> {
     };
   }
 
-  handleRenameClick = (event: React.SyntheticEvent<HTMLElement>) => {
-    event.preventDefault();
+  handleRenameClick = () => {
     this.setState({ renameFormOpen: true });
   };
 
   handleProfileRename = (name: string) => {
     this.closeRenameForm();
-    this.props.updateProfiles().then(() => {
-      if (!this.props.fromList) {
-        this.context.router.replace(
-          getProfilePath(name, this.props.profile.language, this.props.organization)
-        );
-      }
-    });
+    this.props.updateProfiles().then(
+      () => {
+        if (!this.props.fromList) {
+          this.context.router.replace(
+            getProfilePath(name, this.props.profile.language, this.props.organization)
+          );
+        }
+      },
+      () => {}
+    );
   };
 
   closeRenameForm = () => {
     this.setState({ renameFormOpen: false });
   };
 
-  handleCopyClick = (event: React.SyntheticEvent<HTMLElement>) => {
-    event.preventDefault();
+  handleCopyClick = () => {
     this.setState({ copyFormOpen: true });
   };
 
   handleProfileCopy = (name: string) => {
-    this.props.updateProfiles().then(() => {
-      this.context.router.push(
-        getProfilePath(name, this.props.profile.language, this.props.organization)
-      );
-    });
+    this.props.updateProfiles().then(
+      () => {
+        this.context.router.push(
+          getProfilePath(name, this.props.profile.language, this.props.organization)
+        );
+      },
+      () => {}
+    );
   };
 
   closeCopyForm = () => {
     this.setState({ copyFormOpen: false });
   };
 
-  handleSetDefaultClick = (e: React.SyntheticEvent<HTMLElement>) => {
-    e.preventDefault();
-    setDefaultProfile(this.props.profile.key).then(this.props.updateProfiles);
+  handleSetDefaultClick = () => {
+    setDefaultProfile(this.props.profile.key).then(this.props.updateProfiles, () => {});
   };
 
-  handleDeleteClick = (event: React.SyntheticEvent<HTMLElement>) => {
-    event.preventDefault();
+  handleDeleteClick = () => {
     this.setState({ deleteFormOpen: true });
   };
 
@@ -131,60 +137,64 @@ export default class ProfileActions extends React.PureComponent<Props, State> {
       this.props.organization
     );
 
+    const canActivateRules = actions.edit && !profile.isBuiltIn;
+    const canRename = actions.edit && !profile.isBuiltIn;
+    const canSetAsDefault = actions.setAsDefault && !profile.isDefault;
+    const canDelete = actions.edit && !profile.isDefault && !profile.isBuiltIn;
+
     return (
-      <ul className="dropdown-menu dropdown-menu-right">
-        {actions.edit &&
-          !profile.isBuiltIn && (
-            <li>
-              <Link to={activateMoreUrl}>{translate('quality_profiles.activate_more_rules')}</Link>
-            </li>
-          )}
+      <ActionsDropdown className={this.props.className}>
+        {canActivateRules && (
+          <ActionsDropdownItem to={activateMoreUrl}>
+            {translate('quality_profiles.activate_more_rules')}
+          </ActionsDropdownItem>
+        )}
+
         {!profile.isBuiltIn && (
-          <li>
-            <a id="quality-profile-backup" href={backupUrl}>
-              {translate('backup_verb')}
-            </a>
-          </li>
+          <ActionsDropdownItem
+            download={`${profile.key}.xml`}
+            id="quality-profile-backup"
+            to={backupUrl}>
+            {translate('backup_verb')}
+          </ActionsDropdownItem>
         )}
-        <li>
-          <Link
-            to={getProfileComparePath(profile.name, profile.language, this.props.organization)}
-            id="quality-profile-compare">
-            {translate('compare')}
-          </Link>
-        </li>
+
+        <ActionsDropdownItem
+          id="quality-profile-compare"
+          to={getProfileComparePath(profile.name, profile.language, this.props.organization)}>
+          {translate('compare')}
+        </ActionsDropdownItem>
+
         {actions.copy && (
-          <li>
-            <a id="quality-profile-copy" href="#" onClick={this.handleCopyClick}>
-              {translate('copy')}
-            </a>
-          </li>
+          <ActionsDropdownItem id="quality-profile-copy" onClick={this.handleCopyClick}>
+            {translate('copy')}
+          </ActionsDropdownItem>
+        )}
+
+        {canRename && (
+          <ActionsDropdownItem id="quality-profile-rename" onClick={this.handleRenameClick}>
+            {translate('rename')}
+          </ActionsDropdownItem>
+        )}
+
+        {canSetAsDefault && (
+          <ActionsDropdownItem
+            id="quality-profile-set-as-default"
+            onClick={this.handleSetDefaultClick}>
+            {translate('set_as_default')}
+          </ActionsDropdownItem>
+        )}
+
+        {canDelete && <ActionsDropdownDivider />}
+
+        {canDelete && (
+          <ActionsDropdownItem
+            destructive={true}
+            id="quality-profile-delete"
+            onClick={this.handleDeleteClick}>
+            {translate('delete')}
+          </ActionsDropdownItem>
         )}
-        {actions.edit &&
-          !profile.isBuiltIn && (
-            <li>
-              <a id="quality-profile-rename" href="#" onClick={this.handleRenameClick}>
-                {translate('rename')}
-              </a>
-            </li>
-          )}
-        {actions.setAsDefault &&
-          !profile.isDefault && (
-            <li>
-              <a id="quality-profile-set-as-default" href="#" onClick={this.handleSetDefaultClick}>
-                {translate('set_as_default')}
-              </a>
-            </li>
-          )}
-        {actions.edit &&
-          !profile.isDefault &&
-          !profile.isBuiltIn && (
-            <li>
-              <a id="quality-profile-delete" href="#" onClick={this.handleDeleteClick}>
-                {translate('delete')}
-              </a>
-            </li>
-          )}
 
         {this.state.copyFormOpen && (
           <CopyProfileForm
@@ -212,7 +222,7 @@ export default class ProfileActions extends React.PureComponent<Props, State> {
             profile={profile}
           />
         )}
-      </ul>
+      </ActionsDropdown>
     );
   }
 }
index dae1d0887fccf3196929718a7c95a6949cae24eb..ba760e5cb626d0e058c749f823a3c85c419249e6 100644 (file)
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`renders with all permissions 1`] = `
-<ul
-  className="dropdown-menu dropdown-menu-right"
->
-  <li>
-    <Link
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to="/organizations/org/rules#qprofile=foo|activation=false"
-    >
-      quality_profiles.activate_more_rules
-    </Link>
-  </li>
-  <li>
-    <a
-      href="/api/qualityprofiles/backup?profileKey=foo"
-      id="quality-profile-backup"
-    >
-      backup_verb
-    </a>
-  </li>
-  <li>
-    <Link
-      id="quality-profile-compare"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/organizations/org/quality_profiles/compare",
-          "query": Object {
-            "language": "java",
-            "name": "Foo",
-          },
-        }
+<ActionsDropdown>
+  <ActionsDropdownItem
+    to="/organizations/org/rules#qprofile=foo|activation=false"
+  >
+    quality_profiles.activate_more_rules
+  </ActionsDropdownItem>
+  <ActionsDropdownItem
+    download="foo.xml"
+    id="quality-profile-backup"
+    to="/api/qualityprofiles/backup?profileKey=foo"
+  >
+    backup_verb
+  </ActionsDropdownItem>
+  <ActionsDropdownItem
+    id="quality-profile-compare"
+    to={
+      Object {
+        "pathname": "/organizations/org/quality_profiles/compare",
+        "query": Object {
+          "language": "java",
+          "name": "Foo",
+        },
       }
-    >
-      compare
-    </Link>
-  </li>
-  <li>
-    <a
-      href="#"
-      id="quality-profile-copy"
-      onClick={[Function]}
-    >
-      copy
-    </a>
-  </li>
-  <li>
-    <a
-      href="#"
-      id="quality-profile-rename"
-      onClick={[Function]}
-    >
-      rename
-    </a>
-  </li>
-  <li>
-    <a
-      href="#"
-      id="quality-profile-set-as-default"
-      onClick={[Function]}
-    >
-      set_as_default
-    </a>
-  </li>
-  <li>
-    <a
-      href="#"
-      id="quality-profile-delete"
-      onClick={[Function]}
-    >
-      delete
-    </a>
-  </li>
-</ul>
+    }
+  >
+    compare
+  </ActionsDropdownItem>
+  <ActionsDropdownItem
+    id="quality-profile-copy"
+    onClick={[Function]}
+  >
+    copy
+  </ActionsDropdownItem>
+  <ActionsDropdownItem
+    id="quality-profile-rename"
+    onClick={[Function]}
+  >
+    rename
+  </ActionsDropdownItem>
+  <ActionsDropdownItem
+    id="quality-profile-set-as-default"
+    onClick={[Function]}
+  >
+    set_as_default
+  </ActionsDropdownItem>
+  <ActionsDropdownDivider />
+  <ActionsDropdownItem
+    destructive={true}
+    id="quality-profile-delete"
+    onClick={[Function]}
+  >
+    delete
+  </ActionsDropdownItem>
+</ActionsDropdown>
 `;
 
 exports[`renders with no permissions 1`] = `
-<ul
-  className="dropdown-menu dropdown-menu-right"
->
-  <li>
-    <a
-      href="/api/qualityprofiles/backup?profileKey=foo"
-      id="quality-profile-backup"
-    >
-      backup_verb
-    </a>
-  </li>
-  <li>
-    <Link
-      id="quality-profile-compare"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/organizations/org/quality_profiles/compare",
-          "query": Object {
-            "language": "java",
-            "name": "Foo",
-          },
-        }
+<ActionsDropdown>
+  <ActionsDropdownItem
+    download="foo.xml"
+    id="quality-profile-backup"
+    to="/api/qualityprofiles/backup?profileKey=foo"
+  >
+    backup_verb
+  </ActionsDropdownItem>
+  <ActionsDropdownItem
+    id="quality-profile-compare"
+    to={
+      Object {
+        "pathname": "/organizations/org/quality_profiles/compare",
+        "query": Object {
+          "language": "java",
+          "name": "Foo",
+        },
       }
-    >
-      compare
-    </Link>
-  </li>
-</ul>
+    }
+  >
+    compare
+  </ActionsDropdownItem>
+</ActionsDropdown>
 `;
 
 exports[`renders with permission to edit 1`] = `
-<ul
-  className="dropdown-menu dropdown-menu-right"
->
-  <li>
-    <Link
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to="/organizations/org/rules#qprofile=foo|activation=false"
-    >
-      quality_profiles.activate_more_rules
-    </Link>
-  </li>
-  <li>
-    <a
-      href="/api/qualityprofiles/backup?profileKey=foo"
-      id="quality-profile-backup"
-    >
-      backup_verb
-    </a>
-  </li>
-  <li>
-    <Link
-      id="quality-profile-compare"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to={
-        Object {
-          "pathname": "/organizations/org/quality_profiles/compare",
-          "query": Object {
-            "language": "java",
-            "name": "Foo",
-          },
-        }
+<ActionsDropdown>
+  <ActionsDropdownItem
+    to="/organizations/org/rules#qprofile=foo|activation=false"
+  >
+    quality_profiles.activate_more_rules
+  </ActionsDropdownItem>
+  <ActionsDropdownItem
+    download="foo.xml"
+    id="quality-profile-backup"
+    to="/api/qualityprofiles/backup?profileKey=foo"
+  >
+    backup_verb
+  </ActionsDropdownItem>
+  <ActionsDropdownItem
+    id="quality-profile-compare"
+    to={
+      Object {
+        "pathname": "/organizations/org/quality_profiles/compare",
+        "query": Object {
+          "language": "java",
+          "name": "Foo",
+        },
       }
-    >
-      compare
-    </Link>
-  </li>
-  <li>
-    <a
-      href="#"
-      id="quality-profile-rename"
-      onClick={[Function]}
-    >
-      rename
-    </a>
-  </li>
-  <li>
-    <a
-      href="#"
-      id="quality-profile-delete"
-      onClick={[Function]}
-    >
-      delete
-    </a>
-  </li>
-</ul>
+    }
+  >
+    compare
+  </ActionsDropdownItem>
+  <ActionsDropdownItem
+    id="quality-profile-rename"
+    onClick={[Function]}
+  >
+    rename
+  </ActionsDropdownItem>
+  <ActionsDropdownDivider />
+  <ActionsDropdownItem
+    destructive={true}
+    id="quality-profile-delete"
+    onClick={[Function]}
+  >
+    delete
+  </ActionsDropdownItem>
+</ActionsDropdown>
 `;
index 5b04d60fdd42ddc840415ebb4fec07c2b8ad2f1d..b131bb089ded48d9eb84d9ee81ae3924cc33880a 100644 (file)
@@ -107,17 +107,13 @@ export default class ProfileHeader extends React.PureComponent<Props> {
               </Link>
             </li>
             <li>
-              <div className="pull-left dropdown">
-                <button className="dropdown-toggle" data-toggle="dropdown">
-                  {translate('actions')} <i className="icon-dropdown" />
-                </button>
-                <ProfileActions
-                  onRequestFail={this.props.onRequestFail}
-                  organization={organization}
-                  profile={profile}
-                  updateProfiles={this.props.updateProfiles}
-                />
-              </div>
+              <ProfileActions
+                className="pull-left"
+                onRequestFail={this.props.onRequestFail}
+                organization={organization}
+                profile={profile}
+                updateProfiles={this.props.updateProfiles}
+              />
             </li>
           </ul>
         </div>
index b838725a9538e62810b374e75d4bb6c2467b7b23..ff75c1cef3beba4fcf5ce645054b4ade316ef285 100644 (file)
@@ -139,18 +139,13 @@ export default class ProfilesListRow extends React.PureComponent<Props> {
           {this.renderUsageDate()}
         </td>
         <td className="quality-profiles-table-actions thin nowrap text-right">
-          <div className="dropdown">
-            <button className="dropdown-toggle" data-toggle="dropdown">
-              <i className="icon-dropdown" />
-            </button>
-            <ProfileActions
-              fromList={true}
-              onRequestFail={this.props.onRequestFail}
-              organization={this.props.organization}
-              profile={this.props.profile}
-              updateProfiles={this.props.updateProfiles}
-            />
-          </div>
+          <ProfileActions
+            fromList={true}
+            onRequestFail={this.props.onRequestFail}
+            organization={this.props.organization}
+            profile={this.props.profile}
+            updateProfiles={this.props.updateProfiles}
+          />
         </td>
       </tr>
     );
index 14b98fceadb9efd1996085a99855bc2647554ec8..76850ecbf0696823ee879d3c776579878658a0a9 100644 (file)
@@ -21,6 +21,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import PrimitiveInput from './PrimitiveInput';
 import { getEmptyValue } from '../../utils';
+import DeleteIcon from '../../../../components/icons-components/DeleteIcon';
 
 export default class MultiValueInput extends React.PureComponent {
   static propTypes = {
@@ -72,7 +73,7 @@ export default class MultiValueInput extends React.PureComponent {
             <button
               className="js-remove-value button-clean"
               onClick={e => this.handleDeleteValue(e, index)}>
-              <i className="icon-delete" />
+              <DeleteIcon />
             </button>
           </div>
         )}
index 845ca62d95253efed2a679f48376c9e6d5fbd606..4016656f336633416d4c21b73737a5b752b47290 100644 (file)
@@ -21,6 +21,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import PrimitiveInput from './PrimitiveInput';
 import { getEmptyValue, getUniqueName } from '../../utils';
+import DeleteIcon from '../../../../components/icons-components/DeleteIcon';
 
 export default class PropertySetInput extends React.PureComponent {
   static propTypes = {
@@ -74,7 +75,7 @@ export default class PropertySetInput extends React.PureComponent {
             <button
               className="js-remove-value button-link"
               onClick={e => this.handleDeleteValue(e, index)}>
-              <i className="icon-delete" />
+              <DeleteIcon className="text-middle" />
             </button>
           )}
         </td>
index fc74db42541b51381148e6324dfc21ce9d500646..6d84a73fc559cc81339d393455f06f3a73c6a1ba 100644 (file)
@@ -20,6 +20,7 @@
 // @flow
 import React from 'react';
 import { createProject, deleteProject } from '../../../api/components';
+import DeleteIcon from '../../../components/icons-components/DeleteIcon';
 import { translate } from '../../../helpers/l10n';
 
 /*::
@@ -116,7 +117,7 @@ export default class ProjectKeyStep extends React.PureComponent {
           <i className="spinner" />
         ) : (
           <button className="button-clean">
-            <i className="icon-delete" />
+            <DeleteIcon />
           </button>
         )}
       </form>
index 8d36627114f923b5f03091b6e953671147535e0d..962b33b1acbf4258d93e934289c91dd205e3709a 100644 (file)
@@ -28,6 +28,14 @@ jest.mock('../../../../api/components', () => ({
   deleteProject: () => Promise.resolve()
 }));
 
+jest.mock(
+  '../../../../components/icons-components/DeleteIcon',
+  () =>
+    function DeleteIcon() {
+      return null;
+    }
+);
+
 it('creates new project', async () => {
   const onDone = jest.fn();
   const wrapper = mount(<ProjectKeyStep onDelete={jest.fn()} onDone={onDone} />);
index cbfbf1da7ca36a85f58ee5a1bfa3fe9caaa4f059..21ad43db2e6c50d422b9a886ba75b50a79b8902b 100644 (file)
@@ -105,9 +105,7 @@ exports[`creates new project 3`] = `
       <button
         className="button-clean"
       >
-        <i
-          className="icon-delete"
-        />
+        <DeleteIcon />
       </button>
     </form>
   </div>
@@ -138,9 +136,7 @@ exports[`deletes project 1`] = `
       <button
         className="button-clean"
       >
-        <i
-          className="icon-delete"
-        />
+        <DeleteIcon />
       </button>
     </form>
   </div>
index 6ff6e8692e114d4ebff71385d755318ad3d00223..5ecce84bcdd9c2f46cdfbe06adb8abe3e92ed52f 100644 (file)
@@ -7,7 +7,7 @@
     {{tp 'users.deactivate_user.confirmation' name login}}
   </div>
   <div class="modal-foot">
-    <button id="deactivate-user-submit">{{t 'users.deactivate'}}</button>
+    <button class="button-red" id="deactivate-user-submit">{{t 'users.deactivate'}}</button>
     <a href="#" class="js-modal-close" id="deactivate-user-cancel">{{t 'cancel'}}</a>
   </div>
 </form>
index 63136b96402e31fc1f6ac90fceafa29341ea792e..5ac0db657c7a8af4337503f07eaed4ef426bf9f9 100644 (file)
 </td>
 
 <td class="thin nowrap text-right">
-  <a class="js-user-update icon-edit little-spacer-right" title="{{t 'update_details'}}" data-toggle="tooltip" href="#"></a>
-  {{#if local}}
-    <a class="js-user-change-password icon-lock little-spacer-right" title="{{t 'my_profile.password.title'}}" data-toggle="tooltip"
-       href="#"></a>
-  {{/if}}
-  <a class="js-user-deactivate icon-delete" title="{{t 'users.deactivate'}}" data-toggle="tooltip" href="#"></a>
+  <div class="dropdown">
+    <button class="dropdown-toggle" data-toggle="dropdown">
+      {{settingsIcon}}<i class="icon-dropdown little-spacer-left" />
+    </button>
+    <ul class="dropdown-menu dropdown-menu-right">
+      <li>
+        <a class="js-user-update" href="#">{{t 'update_details'}}</a>
+      </li>
+      {{#if local}}
+        <li>
+          <a class="js-user-change-password" href="#">{{t 'my_profile.password.title'}}</a>
+        </li>
+      {{/if}}
+      <li class="divider" />
+      <li>
+        <a class="js-user-deactivate text-danger" href="#">{{t 'users.deactivate'}}</a>
+      </li>
+    </ul>
+  </div>
 </td>
diff --git a/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx b/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx
new file mode 100644 (file)
index 0000000..fbcb30b
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:contact 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 { Link } from 'react-router';
+import { LocationDescriptor } from 'history';
+import SettingsIcon from '../icons-components/SettingsIcon';
+
+interface Props {
+  className?: string;
+  children: React.ReactNode;
+  small?: boolean;
+  toggleClassName?: string;
+}
+
+export default function ActionsDropdown(props: Props) {
+  return (
+    <div className={classNames('dropdown', props.className)}>
+      <button
+        className={classNames('dropdown-toggle', props.toggleClassName, {
+          'button-small button-compact': props.small
+        })}
+        data-toggle="dropdown">
+        <SettingsIcon className="text-text-bottom" />
+        <i className="icon-dropdown little-spacer-left" />
+      </button>
+      <ul className="dropdown-menu dropdown-menu-right">{props.children}</ul>
+    </div>
+  );
+}
+
+interface ItemProps {
+  className?: string;
+  children: React.ReactNode;
+  destructive?: boolean;
+  /** used to pass a name of downloaded file */
+  download?: string;
+  id?: string;
+  onClick?: () => void;
+  to?: LocationDescriptor;
+}
+
+export class ActionsDropdownItem extends React.PureComponent<ItemProps> {
+  handleClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+    event.preventDefault();
+    event.currentTarget.blur();
+    if (this.props.onClick) {
+      this.props.onClick();
+    }
+  };
+
+  render() {
+    const className = classNames(this.props.className, { 'text-danger': this.props.destructive });
+
+    if (this.props.download && typeof this.props.to === 'string') {
+      return (
+        <li>
+          <a
+            className={className}
+            download={this.props.download}
+            href={this.props.to}
+            id={this.props.id}>
+            {this.props.children}
+          </a>
+        </li>
+      );
+    }
+
+    if (this.props.to) {
+      return (
+        <li>
+          <Link className={className} id={this.props.id} to={this.props.to}>
+            {this.props.children}
+          </Link>
+        </li>
+      );
+    }
+
+    return (
+      <li>
+        <a className={className} href="#" id={this.props.id} onClick={this.handleClick}>
+          {this.props.children}
+        </a>
+      </li>
+    );
+  }
+}
+
+export function ActionsDropdownDivider() {
+  return <li className="divider" />;
+}
index f6c7304428223772d92a7361ea46a338f7cabb47..79c2f1c7b18fb65a87f90e3ff10cbc3e4f70009f 100644 (file)
@@ -21,6 +21,8 @@
 import React from 'react';
 import Avatar from '../../../components/ui/Avatar';
 import BubblePopupHelper from '../../../components/common/BubblePopupHelper';
+import EditIcon from '../../../components/icons-components/EditIcon';
+import DeleteIcon from '../../../components/icons-components/DeleteIcon';
 import CommentDeletePopup from '../popups/CommentDeletePopup';
 import CommentPopup from '../popups/CommentPopup';
 import DateFromNow from '../../../components/intl/DateFromNow';
@@ -118,9 +120,10 @@ export default class IssueCommentLine extends React.PureComponent {
                 />
               }>
               <button
-                className="js-issue-comment-edit button-link icon-edit icon-half-transparent"
-                onClick={this.toggleEditPopup}
-              />
+                className="js-issue-comment-edit button-link icon-half-transparent"
+                onClick={this.toggleEditPopup}>
+                <EditIcon />
+              </button>
             </BubblePopupHelper>
           )}
           {comment.updatable && (
@@ -132,9 +135,10 @@ export default class IssueCommentLine extends React.PureComponent {
               togglePopup={this.toggleDeletePopup}
               popup={<CommentDeletePopup onDelete={this.handleDelete} />}>
               <button
-                className="js-issue-comment-delete button-link icon-delete icon-half-transparent"
-                onClick={this.toggleDeletePopup}
-              />
+                className="js-issue-comment-delete button-link icon-half-transparent little-spacer-left"
+                onClick={this.toggleDeletePopup}>
+                <DeleteIcon />
+              </button>
             </BubblePopupHelper>
           )}
         </div>
index d31a9e8eba2ceed67a4a69493200b6bea31d87a7..2ffdaa63fd416fb18e6565a9a3b527a578aadf5d 100644 (file)
@@ -80,9 +80,11 @@ exports[`should open the right popups when the buttons are clicked 3`] = `
       togglePopup={[Function]}
     >
       <button
-        className="js-issue-comment-edit button-link icon-edit icon-half-transparent"
+        className="js-issue-comment-edit button-link icon-half-transparent"
         onClick={[Function]}
-      />
+      >
+        <EditIcon />
+      </button>
     </BubblePopupHelper>
     <BubblePopupHelper
       className="bubble-popup-helper-inline"
@@ -102,9 +104,11 @@ exports[`should open the right popups when the buttons are clicked 3`] = `
       togglePopup={[Function]}
     >
       <button
-        className="js-issue-comment-delete button-link icon-delete icon-half-transparent"
+        className="js-issue-comment-delete button-link icon-half-transparent little-spacer-left"
         onClick={[Function]}
-      />
+      >
+        <DeleteIcon />
+      </button>
     </BubblePopupHelper>
   </div>
 </div>
@@ -218,9 +222,11 @@ exports[`should render correctly a comment that is updatable 1`] = `
       togglePopup={[Function]}
     >
       <button
-        className="js-issue-comment-edit button-link icon-edit icon-half-transparent"
+        className="js-issue-comment-edit button-link icon-half-transparent"
         onClick={[Function]}
-      />
+      >
+        <EditIcon />
+      </button>
     </BubblePopupHelper>
     <BubblePopupHelper
       className="bubble-popup-helper-inline"
@@ -240,9 +246,11 @@ exports[`should render correctly a comment that is updatable 1`] = `
       togglePopup={[Function]}
     >
       <button
-        className="js-issue-comment-delete button-link icon-delete icon-half-transparent"
+        className="js-issue-comment-delete button-link icon-half-transparent little-spacer-left"
         onClick={[Function]}
-      />
+      >
+        <DeleteIcon />
+      </button>
     </BubblePopupHelper>
   </div>
 </div>
diff --git a/server/sonar-web/src/main/js/helpers/handlebars/settingsIcon.js b/server/sonar-web/src/main/js/helpers/handlebars/settingsIcon.js
new file mode 100644 (file)
index 0000000..48db75b
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.
+ */
+const Handlebars = require('handlebars/runtime');
+
+module.exports = function() {
+  return new Handlebars.default.SafeString(`
+    <svg class="text-text-bottom" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" width="14" height="14">
+      <g transform="matrix(0.0364583,0,0,0.0364583,0,-1.16667)">
+        <path
+          d="M256,224C256,206.333 249.75,191.25 237.25,178.75C224.75,166.25 209.667,160 192,160C174.333,160 159.25,166.25 146.75,178.75C134.25,191.25 128,206.333 128,224C128,241.667 134.25,256.75 146.75,269.25C159.25,281.75 174.333,288 192,288C209.667,288 224.75,281.75 237.25,269.25C249.75,256.75 256,241.667 256,224ZM384,196.75L384,252.25C384,254.25 383.333,256.167 382,258C380.667,259.833 379,260.917 377,261.25L330.75,268.25C327.583,277.25 324.333,284.833 321,291C326.833,299.333 335.75,310.833 347.75,325.5C349.417,327.5 350.25,329.583 350.25,331.75C350.25,333.917 349.5,335.833 348,337.5C343.5,343.667 335.25,352.667 323.25,364.5C311.25,376.333 303.417,382.25 299.75,382.25C297.75,382.25 295.583,381.5 293.25,380L258.75,353C251.417,356.833 243.833,360 236,362.5C233.333,385.167 230.917,400.667 228.75,409C227.583,413.667 224.583,416 219.75,416L164.25,416C161.917,416 159.875,415.292 158.125,413.875C156.375,412.458 155.417,410.667 155.25,408.5L148.25,362.5C140.083,359.833 132.583,356.75 125.75,353.25L90.5,380C88.833,381.5 86.75,382.25 84.25,382.25C81.917,382.25 79.833,381.333 78,379.5C57,360.5 43.25,346.5 36.75,337.5C35.583,335.833 35,333.917 35,331.75C35,329.75 35.667,327.833 37,326C39.5,322.5 43.75,316.958 49.75,309.375C55.75,301.792 60.25,295.917 63.25,291.75C58.75,283.417 55.333,275.167 53,267L7.25,260.25C5.083,259.917 3.333,258.875 2,257.125C0.667,255.375 0,253.417 0,251.25L0,195.75C0,193.75 0.667,191.833 2,190C3.333,188.167 4.917,187.083 6.75,186.75L53.25,179.75C55.583,172.083 58.833,164.417 63,156.75C56.333,147.25 47.417,135.75 36.25,122.25C34.583,120.25 33.75,118.25 33.75,116.25C33.75,114.583 34.5,112.667 36,110.5C40.333,104.5 48.542,95.542 60.625,83.625C72.708,71.708 80.583,65.75 84.25,65.75C86.417,65.75 88.583,66.583 90.75,68.25L125.25,95C132.583,91.167 140.167,88 148,85.5C150.667,62.833 153.083,47.333 155.25,39C156.417,34.333 159.417,32 164.25,32L219.75,32C222.083,32 224.125,32.708 225.875,34.125C227.625,35.542 228.583,37.333 228.75,39.5L235.75,85.5C243.917,88.167 251.417,91.25 258.25,94.75L293.75,68C295.25,66.5 297.25,65.75 299.75,65.75C301.917,65.75 304,66.583 306,68.25C327.5,88.083 341.25,102.25 347.25,110.75C348.417,112.083 349,113.917 349,116.25C349,118.25 348.333,120.167 347,122C344.5,125.5 340.25,131.042 334.25,138.625C328.25,146.208 323.75,152.083 320.75,156.25C325.083,164.583 328.5,172.75 331,180.75L376.75,187.75C378.917,188.083 380.667,189.125 382,190.875C383.333,192.625 384,194.583 384,196.75Z"
+          style="fill: currentColor"
+        />
+      </g>
+    </svg>
+  `);
+};
index dd2d21ba31f8fedbf236640209edba17c0265fb9..3091176ebaeaa7ce9d464d61f595bbcde7b6a22f 100644 (file)
        <td>/organizations/test-org/quality_profiles</td>
        <td></td>
 </tr>
-<tr>
-       <td>waitForElementPresent</td>
-       <td>css=.js-more-admin-actions</td>
-       <td></td>
-</tr>
-<tr>
-       <td>click</td>
-       <td>css=.js-more-admin-actions</td>
-       <td></td>
-</tr>
 <tr>
        <td>click</td>
        <td>css=#quality-profiles-restore</td>
index 8def772700158cdd750da843ff10e826fc32e5b5..792e12c864c22137a8fe35a7ae8d5abdb4f2245b 100644 (file)
        <td>/profiles</td>
        <td></td>
 </tr>
-<tr>
-       <td>waitForElementPresent</td>
-       <td>css=.js-more-admin-actions</td>
-       <td></td>
-</tr>
-<tr>
-       <td>click</td>
-       <td>css=.js-more-admin-actions</td>
-       <td></td>
-</tr>
 <tr>
        <td>click</td>
        <td>css=#quality-profiles-restore</td>
index f7d0dd6ffb6ded62e9633cb3f548616452e1c571..9be06a9320a51117336a811127af7682305647e0 100644 (file)
        <td>id=users-list</td>
        <td>*${userId}*</td>
 </tr>
+<tr>
+       <td>click</td>
+       <td>css=[data-login=&quot;${userId}&quot;] .dropdown-toggle</td>
+       <td></td>
+</tr>
 <tr>
        <td>click</td>
        <td>css=[data-login=&quot;${userId}&quot;] .js-user-deactivate</td>
index bc17a7e90c98a48cb2a8cc693cd927347465342d..228686fcd62751372251e38ae872efa523e79450 100644 (file)
        <td>css=[data-login=admin-user]</td>
        <td></td>
 </tr>
+<tr>
+       <td>click</td>
+       <td>css=[data-login=admin-user] .dropdown-toggle</td>
+       <td></td>
+</tr>
 <tr>
        <td>click</td>
        <td>css=[data-login=admin-user] .js-user-change-password</td>