]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19345 - Fixed several issues MUI
authorKevin Silva <kevin.silva@sonarsource.com>
Wed, 14 Jun 2023 11:04:15 +0000 (13:04 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 15 Jun 2023 09:41:52 +0000 (09:41 +0000)
17 files changed:
server/sonar-web/design-system/src/components/SearchSelectDropdown.tsx
server/sonar-web/design-system/src/components/popups.tsx
server/sonar-web/design-system/src/helpers/positioning.ts
server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx
server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx
server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx
server/sonar-web/src/main/js/apps/issues/components/ListItem.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesList.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssueList-test.tsx.snap
server/sonar-web/src/main/js/components/issue/Issue.tsx
server/sonar-web/src/main/js/components/issue/__tests__/Issue-it.tsx
server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx
server/sonar-web/src/main/js/components/issue/components/IssueAssign.tsx
server/sonar-web/src/main/js/components/issue/components/IssueCommentAction.tsx
server/sonar-web/src/main/js/components/issue/components/IssueMessage.tsx
server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx
server/sonar-web/src/main/js/components/issue/components/IssueView.tsx

index 18e724c1ed2e16a0d91f79b1b3721979b7de2c90..96480ed7d7e4597dab25cf2bb94e07e0df3be6cc 100644 (file)
@@ -58,6 +58,7 @@ export interface SearchSelectDropdownProps<
   controlLabel?: React.ReactNode | string;
   controlSize?: InputSizeKeys;
   isDiscreet?: boolean;
+  zLevel?: PopupZLevel;
 }
 
 export function SearchSelectDropdown<
@@ -77,6 +78,7 @@ export function SearchSelectDropdown<
     minLength,
     controlAriaLabel,
     menuIsOpen,
+    zLevel = PopupZLevel.Global,
     ...rest
   } = props;
   const [open, setOpen] = React.useState(false);
@@ -161,7 +163,7 @@ export function SearchSelectDropdown<
         </SearchHighlighterContext.Provider>
       }
       placement={PopupPlacement.BottomLeft}
-      zLevel={PopupZLevel.Global}
+      zLevel={zLevel}
     >
       <SearchSelectDropdownControl
         ariaLabel={controlAriaLabel}
index 0c24ad06213a168a75cf3e14ba91c5313a15b121..a80d9b949ea6e3b63c25968953645a2f704d0860 100644 (file)
@@ -24,7 +24,7 @@ import React, { AriaRole } from 'react';
 import { createPortal, findDOMNode } from 'react-dom';
 import tw from 'twin.macro';
 import { THROTTLE_SCROLL_DELAY } from '../helpers/constants';
-import { PopupPlacement, popupPositioning, PopupZLevel } from '../helpers/positioning';
+import { PopupPlacement, PopupZLevel, popupPositioning } from '../helpers/positioning';
 import { themeBorder, themeColor, themeContrast, themeShadow } from '../helpers/theme';
 import ClickEventBoundary from './ClickEventBoundary';
 
@@ -129,7 +129,7 @@ export class Popup extends React.PureComponent<PopupProps, State> {
   };
 
   positionPopup = () => {
-    if (this.mounted) {
+    if (this.mounted && this.props.zLevel !== PopupZLevel.Absolute) {
       // `findDOMNode(this)` will search for the DOM node for the current component
       // first it will find a React.Fragment (see `render`),
       // so it will get the DOM node of the first child, i.e. DOM node of `this.props.children`
@@ -216,6 +216,7 @@ const PopupWrapper = styled.div<{ zLevel: PopupZLevel }>`
       [PopupZLevel.Default]: tw`sw-z-popup`,
       [PopupZLevel.Global]: tw`sw-z-global-popup`,
       [PopupZLevel.Content]: tw`sw-z-content-popup`,
+      [PopupZLevel.Absolute]: tw`sw-z-global-popup`,
     }[zLevel])};
 
   &.is-bottom,
index 4cceaccc23d516d4929270b0d916829fe70907cc..47afd6b3f49963a05df51b65efba739fcca3e947 100644 (file)
@@ -52,6 +52,7 @@ export enum PopupZLevel {
   Content = 'content',
   Default = 'popup',
   Global = 'global',
+  Absolute = 'absolute',
 }
 
 export type BasePlacement = Extract<
index 8f61c516b0a7ede4c3a4538a8fbf3eb0d6e318f4..4715c45f80778650a0752ef71ce48f11607de5a2 100644 (file)
@@ -69,19 +69,14 @@ export default class IssueHeader extends React.PureComponent<Props, State> {
   }
 
   handleIssuePopupToggle = (popupName: string, open?: boolean) => {
-    const openPopupState = { issuePopupName: popupName };
-
-    const closePopupState = { issuePopupName: undefined };
-
     this.setState(({ issuePopupName }) => {
-      if (open) {
-        return openPopupState;
-      } else if (open === false) {
-        return closePopupState;
+      const samePopup = popupName && issuePopupName === popupName;
+      if (open !== false && !samePopup) {
+        return { issuePopupName: popupName };
+      } else if (open !== true && samePopup) {
+        return { issuePopupName: undefined };
       }
-
-      // toggle popup
-      return issuePopupName === popupName ? closePopupState : openPopupState;
+      return { issuePopupName };
     });
   };
 
index 87184179c238ba8c4f620f0070550387948738b7..d6439f3c0b78e0cdfd8c69ff382dfd51aebb38ee 100644 (file)
@@ -430,6 +430,14 @@ export class App extends React.PureComponent<Props, State> {
     }
   };
 
+  selectIssue = (issueKey: string) => {
+    this.setState({
+      selected: issueKey,
+      selectedFlowIndex: undefined,
+      selectedLocationIndex: undefined,
+    });
+  };
+
   closeIssue = () => {
     if (this.state.query) {
       this.props.router.push({
@@ -1124,6 +1132,7 @@ export class App extends React.PureComponent<Props, State> {
             onIssueChange={this.handleIssueChange}
             onIssueCheck={currentUser.isLoggedIn ? this.handleIssueCheck : undefined}
             onIssueClick={this.openIssue}
+            onIssueSelect={this.selectIssue}
             onPopupToggle={this.handlePopupToggle}
             openPopup={this.state.openPopup}
             selectedIssue={selectedIssue}
index 6cf790b1cf1a66f4bb63a98ca4b33c4c88da2349..e5c4d2a15f18848a028d5a5b825cc0d423665687 100644 (file)
@@ -34,6 +34,7 @@ interface Props {
   onIssueChange: (issue: Issue) => void;
   onIssueCheck: ((issueKey: string) => void) | undefined;
   onIssueClick: (issueKey: string) => void;
+  onIssueSelect: (issueKey: string) => void;
   onPopupToggle: (issue: string, popupName: string, open?: boolean) => void;
   openPopup: { issue: string; name: string } | undefined;
   selectedIssue: Issue | undefined;
@@ -77,6 +78,7 @@ export default class IssuesList extends React.PureComponent<Props, State> {
               onChange={this.props.onIssueChange}
               onCheck={this.props.onIssueCheck}
               onClick={this.props.onIssueClick}
+              onSelect={this.props.onIssueSelect}
               onFilterChange={this.props.onFilterChange}
               onPopupToggle={this.props.onPopupToggle}
               openPopup={openPopup && openPopup.issue === issue.key ? openPopup.name : undefined}
index f0b1af18a55dad36a0e4afe8a2abe181bb6752c8..8f44dfb1566f50e43874f1b4c2030e1f1d5812c3 100644 (file)
@@ -30,6 +30,7 @@ interface Props {
   onChange: (issue: TypeIssue) => void;
   onCheck: ((issueKey: string) => void) | undefined;
   onClick: (issueKey: string) => void;
+  onSelect: (issueKey: string) => void;
   onFilterChange: (changes: Partial<Query>) => void;
   onPopupToggle: (issue: string, popupName: string, open?: boolean) => void;
   openPopup: string | undefined;
@@ -93,6 +94,7 @@ export default class ListItem extends React.PureComponent<Props> {
         onChange={this.props.onChange}
         onCheck={this.props.onCheck}
         onClick={this.props.onClick}
+        onSelect={this.props.onSelect}
         onPopupToggle={this.props.onPopupToggle}
         openPopup={this.props.openPopup}
         selected={this.props.selected}
index a9d639f376b2e640f227034fa58d78f7aa8e2869..222b8ac10dde193ea40c7cdd652b74f98b8f320d 100644 (file)
@@ -70,7 +70,7 @@ export default function LineIssuesList(props: LineIssuesListProps) {
           issue={issue}
           key={issue.key}
           onChange={props.onIssueChange}
-          onClick={props.onIssueClick}
+          onSelect={props.onIssueClick}
           onPopupToggle={props.onIssuePopupToggle}
           openPopup={issuePopup && issuePopup.issue === issue.key ? issuePopup.name : undefined}
           selected={props.selectedIssue === issue.key}
index 11e954e8499b637f319b96b7c04b854967c20dfd..9a03583ab365acc048b99a0111308359e6ffdd58 100644 (file)
@@ -49,8 +49,8 @@ exports[`should render issues 1`] = `
     }
     key="issue"
     onChange={[MockFunction]}
-    onClick={[MockFunction]}
     onPopupToggle={[MockFunction]}
+    onSelect={[MockFunction]}
     selected={true}
   />
 </div>
index 3afdca167f03cb8c09ae2c1a1ebb04063eb39a09..d1f7bd57f85b25fda338972330c3cdb40fcebaf8 100644 (file)
@@ -35,6 +35,7 @@ interface Props {
   onChange: (issue: TypeIssue) => void;
   onCheck?: (issue: string) => void;
   onClick?: (issueKey: string) => void;
+  onSelect: (issueKey: string) => void;
   onPopupToggle: (issue: string, popupName: string, open?: boolean) => void;
   openPopup?: string;
   selected: boolean;
@@ -119,6 +120,7 @@ export default class Issue extends React.PureComponent<Props> {
         onChange={this.props.onChange}
         onCheck={this.props.onCheck}
         onClick={this.props.onClick}
+        onSelect={this.props.onSelect}
         selected={this.props.selected}
         togglePopup={this.togglePopup}
       />
index dc3166bab0f645e192763eb7844c1bf98b10524e..96517b9dc685c519494f9bda03da0a94557befda 100644 (file)
@@ -52,7 +52,7 @@ describe('rendering', () => {
     const { ui } = getPageObject();
     const issue = mockIssue(true, { effort: '2 days', message: 'This is an issue' });
     const onClick = jest.fn();
-    renderIssue({ issue, onClick });
+    renderIssue({ issue, onSelect: onClick });
 
     expect(ui.effort('2 days').get()).toBeInTheDocument();
     await ui.clickIssueMessage();
@@ -432,7 +432,11 @@ function renderIssue(props: Partial<Omit<Issue['props'], 'onChange' | 'onPopupTo
     );
   }
 
-  return renderApp('/', <Wrapper issue={mockIssue()} selected={false} {...props} />, {
-    currentUser: mockLoggedInUser({ login: 'leia', name: 'Organa' }),
-  });
+  return renderApp(
+    '/',
+    <Wrapper onSelect={jest.fn()} issue={mockIssue()} selected={false} {...props} />,
+    {
+      currentUser: mockLoggedInUser({ login: 'leia', name: 'Organa' }),
+    }
+  );
 }
index 3230d4c9621c6bc0ad3662e9fc0bf2df9835c882..76fa0f8523143d1fffeb71880d98a875fc364ded 100644 (file)
@@ -162,7 +162,7 @@ export default function IssueActionsBar(props: Props) {
         <IssueCommentAction
           commentAutoTriggered={commentState.commentAutoTriggered}
           commentPlaceholder={commentState.commentPlaceholder}
-          currentPopup={currentPopup}
+          currentPopup={currentPopup === 'comment'}
           issueKey={issue.key}
           onChange={onChange}
           toggleComment={toggleComment}
index 3b40a482240c304b7a070b07674382adcc983c5c..4ca851e5a3901243518253cacc50fe79fe4cb9f7 100644 (file)
@@ -17,7 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { LabelValueSelectOption, SearchSelectDropdown } from 'design-system';
+import { LabelValueSelectOption, PopupZLevel, SearchSelectDropdown } from 'design-system';
 import * as React from 'react';
 import { Options, SingleValue } from 'react-select';
 import { searchUsers } from '../../../api/users';
@@ -132,26 +132,29 @@ export default function IssueAssignee(props: Props) {
   }
 
   return (
-    <SearchSelectDropdown
-      size="medium"
-      className="it__issue-assign"
-      controlAriaLabel={
-        assinedUser
-          ? translateWithParameters('issue.assign.assigned_to_x_click_to_change', assinedUser)
-          : translate('issue.assign.unassigned_click_to_assign')
-      }
-      defaultOptions={defaultOptions}
-      onChange={handleAssign}
-      loadOptions={handleSearchAssignees}
-      menuIsOpen={props.isOpen}
-      minLength={minSearchLength}
-      onMenuOpen={() => toggleAssign(true)}
-      onMenuClose={handleClose}
-      isDiscreet
-      controlLabel={controlLabel}
-      tooShortText={translateWithParameters('search.tooShort', String(minSearchLength))}
-      placeholder={translate('search.search_for_users')}
-      aria-label={translate('search.search_for_users')}
-    />
+    <div className="sw-relative">
+      <SearchSelectDropdown
+        size="medium"
+        className="it__issue-assign"
+        controlAriaLabel={
+          assinedUser
+            ? translateWithParameters('issue.assign.assigned_to_x_click_to_change', assinedUser)
+            : translate('issue.assign.unassigned_click_to_assign')
+        }
+        defaultOptions={defaultOptions}
+        onChange={handleAssign}
+        loadOptions={handleSearchAssignees}
+        menuIsOpen={props.isOpen}
+        minLength={minSearchLength}
+        onMenuOpen={() => toggleAssign(true)}
+        onMenuClose={handleClose}
+        isDiscreet
+        controlLabel={controlLabel}
+        tooShortText={translateWithParameters('search.tooShort', String(minSearchLength))}
+        placeholder={translate('search.search_for_users')}
+        aria-label={translate('search.search_for_users')}
+        zLevel={PopupZLevel.Absolute}
+      />
+    </div>
   );
 }
index d3e4627b93e96179ae3295009dd60b57eb243d47..17ccba55a76f4d3717c2316d2f32a833baf26757 100644 (file)
@@ -29,7 +29,7 @@ interface Props {
   canComment: boolean;
   commentAutoTriggered?: boolean;
   commentPlaceholder: string;
-  currentPopup?: string;
+  currentPopup?: boolean;
   issueKey: string;
   onChange: (issue: Issue) => void;
   toggleComment: (open?: boolean, placeholder?: string, autoTriggered?: boolean) => void;
@@ -69,7 +69,7 @@ export default class IssueCommentAction extends React.PureComponent<Props> {
         <Toggler
           closeOnClickOutside={false}
           onRequestClose={this.handleClose}
-          open={this.props.currentPopup === 'comment'}
+          open={!!this.props.currentPopup}
           overlay={
             showCommentsInPopup ? (
               <CommentListPopup
index 45993e6e6923b1d6306773df41713a4e0108d283..3cb8cebbcd2df34144d29e4faa4ccdda05e37635 100644 (file)
@@ -23,11 +23,11 @@ import { getBranchLikeQuery } from '../../../helpers/branch-like';
 import { translate } from '../../../helpers/l10n';
 import { getComponentIssuesUrl } from '../../../helpers/urls';
 import { BranchLike } from '../../../types/branch-like';
+import { IssueType } from '../../../types/issues';
 import { Issue } from '../../../types/types';
 import { IssueMessageHighlighting } from '../IssueMessageHighlighting';
 
 export interface IssueMessageProps {
-  onClick?: () => void;
   issue: Issue;
   branchLike?: BranchLike;
   displayWhyIsThisAnIssue?: boolean;
@@ -46,10 +46,15 @@ export default function IssueMessage(props: IssueMessageProps) {
     why: '1',
   });
 
+  const issueUrl = getComponentIssuesUrl(issue.project, {
+    ...getBranchLikeQuery(branchLike),
+    open: issue.key,
+    types: issue.type === IssueType.SecurityHotspot ? issue.type : undefined,
+  });
   return (
     <>
-      {props.onClick ? (
-        <StandoutLink onClick={props.onClick} className="it__issue-message" preventDefault to={{}}>
+      {issueUrl?.pathname ? (
+        <StandoutLink className="it__issue-message" to={issueUrl}>
           <IssueMessageHighlighting message={message} messageFormattings={messageFormattings} />
         </StandoutLink>
       ) : (
index 36e52814a81cb69c70ad9dfc8df2ccbbb9068c38..8959b7fb4c3eb75fbf4409e95b1d4a799487605c 100644 (file)
@@ -28,7 +28,6 @@ import IssueTags from './IssueTags';
 export interface IssueTitleBarProps {
   currentPopup?: string;
   branchLike?: BranchLike;
-  onClick?: () => void;
   displayWhyIsThisAnIssue?: boolean;
   issue: Issue;
   onChange: (issue: Issue) => void;
@@ -46,7 +45,6 @@ export default function IssueTitleBar(props: IssueTitleBarProps) {
           issue={issue}
           branchLike={props.branchLike}
           displayWhyIsThisAnIssue={displayWhyIsThisAnIssue}
-          onClick={props.onClick}
         />
       </div>
       <div className="js-issue-tags sw-body-sm sw-grow-0 sw-whitespace-nowrap">
index ca7250d3af17f3e520f48b1b61f9ee42ce476842..23a6fd2750979c16099336b4f7689021ac2ec7d5 100644 (file)
@@ -38,6 +38,7 @@ interface Props {
   onAssign: (login: string) => void;
   onChange: (issue: Issue) => void;
   onCheck?: (issue: string) => void;
+  onSelect: (issueKey: string) => void;
   onClick?: (issueKey: string) => void;
   selected: boolean;
   togglePopup: (popup: string, show: boolean | void) => void;
@@ -49,14 +50,14 @@ export default class IssueView extends React.PureComponent<Props> {
   componentDidMount() {
     const { selected } = this.props;
     if (this.nodeRef && selected) {
-      this.nodeRef.scrollIntoView({ block: 'center', inline: 'center' });
+      this.nodeRef.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
     }
   }
 
   componentDidUpdate(prevProps: Props) {
     const { selected } = this.props;
     if (!prevProps.selected && selected && this.nodeRef) {
-      this.nodeRef.scrollIntoView({ block: 'center', inline: 'center' });
+      this.nodeRef.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
     }
   }
 
@@ -66,19 +67,6 @@ export default class IssueView extends React.PureComponent<Props> {
     }
   };
 
-  handleBoxClick = (event: React.MouseEvent<HTMLDivElement>) => {
-    if (!isClickable(event.target as HTMLElement) && this.props.onClick) {
-      event.preventDefault();
-      this.handleDetailClick();
-    }
-  };
-
-  handleDetailClick = () => {
-    if (this.props.onClick) {
-      this.props.onClick(this.props.issue.key);
-    }
-  };
-
   editComment = (comment: string, text: string) => {
     updateIssue(this.props.onChange, editIssueComment({ comment, text }));
   };
@@ -102,6 +90,7 @@ export default class IssueView extends React.PureComponent<Props> {
 
     return (
       <IssueItem
+        onClick={() => this.props.onSelect(issue.key)}
         className={issueClass}
         role="region"
         aria-label={issue.message}
@@ -120,7 +109,6 @@ export default class IssueView extends React.PureComponent<Props> {
             <IssueTitleBar
               currentPopup={currentPopup}
               branchLike={branchLike}
-              onClick={this.handleDetailClick}
               displayWhyIsThisAnIssue={displayWhyIsThisAnIssue}
               issue={issue}
               onChange={this.props.onChange}
@@ -141,15 +129,6 @@ export default class IssueView extends React.PureComponent<Props> {
   }
 }
 
-function isClickable(node: HTMLElement | undefined | null): boolean {
-  if (!node) {
-    return false;
-  }
-  const clickableTags = ['A', 'BUTTON', 'INPUT', 'TEXTAREA'];
-  const tagName = (node.tagName || '').toUpperCase();
-  return clickableTags.includes(tagName) || isClickable(node.parentElement);
-}
-
 const IssueItem = styled.li`
   box-sizing: border-box;
   border: ${themeBorder('default', 'transparent')};