Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

IssueActionsBar.tsx 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import styled from '@emotion/styled';
  21. import classNames from 'classnames';
  22. import { Badge, CommentIcon, SeparatorCircleIcon, themeColor } from 'design-system';
  23. import * as React from 'react';
  24. import { translate, translateWithParameters } from '../../../helpers/l10n';
  25. import { isDefined } from '../../../helpers/types';
  26. import {
  27. IssueActions,
  28. IssueResolution,
  29. IssueResponse,
  30. IssueType as IssueTypeEnum,
  31. } from '../../../types/issues';
  32. import { RuleStatus } from '../../../types/rules';
  33. import { Issue, RawQuery } from '../../../types/types';
  34. import Tooltip from '../../controls/Tooltip';
  35. import DateFromNow from '../../intl/DateFromNow';
  36. import { WorkspaceContext } from '../../workspace/context';
  37. import { updateIssue } from '../actions';
  38. import IssueAssign from './IssueAssign';
  39. import IssueBadges from './IssueBadges';
  40. import IssueCommentAction from './IssueCommentAction';
  41. import IssueSeverity from './IssueSeverity';
  42. import IssueTransition from './IssueTransition';
  43. import IssueType from './IssueType';
  44. interface Props {
  45. issue: Issue;
  46. currentPopup?: string;
  47. onAssign: (login: string) => void;
  48. onChange: (issue: Issue) => void;
  49. togglePopup: (popup: string, show?: boolean) => void;
  50. className?: string;
  51. showComments?: boolean;
  52. showLine?: boolean;
  53. }
  54. interface State {
  55. commentAutoTriggered: boolean;
  56. commentPlaceholder: string;
  57. }
  58. export default function IssueActionsBar(props: Props) {
  59. const {
  60. issue,
  61. currentPopup,
  62. onAssign,
  63. onChange,
  64. togglePopup,
  65. className,
  66. showComments,
  67. showLine,
  68. } = props;
  69. const [commentState, setCommentState] = React.useState<State>({
  70. commentAutoTriggered: false,
  71. commentPlaceholder: '',
  72. });
  73. const setIssueProperty = (
  74. property: keyof Issue,
  75. popup: string,
  76. apiCall: (query: RawQuery) => Promise<IssueResponse>,
  77. value: string
  78. ) => {
  79. if (issue[property] !== value) {
  80. const newIssue = { ...issue, [property]: value };
  81. updateIssue(onChange, apiCall({ issue: issue.key, [property]: value }), issue, newIssue);
  82. }
  83. togglePopup(popup, false);
  84. };
  85. const toggleComment = (open: boolean, placeholder = '', autoTriggered = false) => {
  86. setCommentState({
  87. commentPlaceholder: placeholder,
  88. commentAutoTriggered: autoTriggered,
  89. });
  90. togglePopup('comment', open);
  91. };
  92. const handleTransition = (issue: Issue) => {
  93. onChange(issue);
  94. if (
  95. issue.resolution === IssueResolution.FalsePositive ||
  96. (issue.resolution === IssueResolution.WontFix && issue.type !== IssueTypeEnum.SecurityHotspot)
  97. ) {
  98. toggleComment(true, translate('issue.comment.explain_why'), true);
  99. }
  100. };
  101. const { externalRulesRepoNames } = React.useContext(WorkspaceContext);
  102. const ruleEngine =
  103. (issue.externalRuleEngine && externalRulesRepoNames[issue.externalRuleEngine]) ||
  104. issue.externalRuleEngine;
  105. const canAssign = issue.actions.includes(IssueActions.Assign);
  106. const canComment = issue.actions.includes(IssueActions.Comment);
  107. const canSetSeverity = issue.actions.includes(IssueActions.SetSeverity);
  108. const canSetType = issue.actions.includes(IssueActions.SetType);
  109. const hasTransitions = issue.transitions.length > 0;
  110. const hasComments = !!issue.comments?.length;
  111. const issueMetaListItemClassNames = classNames(
  112. className,
  113. 'sw-body-sm sw-overflow-hidden sw-whitespace-nowrap sw-max-w-abs-150'
  114. );
  115. return (
  116. <div
  117. className={classNames(className, 'sw-flex sw-flex-wrap sw-items-center sw-justify-between')}
  118. >
  119. <ul className="it__issue-header-actions sw-flex sw-items-center sw-gap-3 sw-body-sm">
  120. <li>
  121. <IssueType canSetType={canSetType} issue={issue} setIssueProperty={setIssueProperty} />
  122. </li>
  123. <li>
  124. <IssueTransition
  125. isOpen={currentPopup === 'transition'}
  126. togglePopup={togglePopup}
  127. hasTransitions={hasTransitions}
  128. issue={issue}
  129. onChange={handleTransition}
  130. />
  131. </li>
  132. <li>
  133. <IssueSeverity
  134. isOpen={currentPopup === 'set-severity'}
  135. togglePopup={togglePopup}
  136. canSetSeverity={canSetSeverity}
  137. issue={issue}
  138. setIssueProperty={setIssueProperty}
  139. />
  140. </li>
  141. <li>
  142. <IssueAssign
  143. isOpen={currentPopup === 'assign'}
  144. togglePopup={togglePopup}
  145. canAssign={canAssign}
  146. issue={issue}
  147. onAssign={onAssign}
  148. />
  149. </li>
  150. </ul>
  151. {canComment && (
  152. <IssueCommentAction
  153. commentAutoTriggered={commentState.commentAutoTriggered}
  154. commentPlaceholder={commentState.commentPlaceholder}
  155. currentPopup={currentPopup === 'comment'}
  156. issueKey={issue.key}
  157. onChange={onChange}
  158. toggleComment={toggleComment}
  159. />
  160. )}
  161. <ul className="sw-flex sw-items-center sw-gap-2 sw-body-sm">
  162. <li className={issueMetaListItemClassNames}>
  163. <IssueBadges
  164. quickFixAvailable={issue.quickFixAvailable}
  165. ruleStatus={issue.ruleStatus as RuleStatus | undefined}
  166. />
  167. </li>
  168. {ruleEngine && (
  169. <li className={issueMetaListItemClassNames}>
  170. <Tooltip
  171. overlay={translateWithParameters('issue.from_external_rule_engine', ruleEngine)}
  172. >
  173. <span>
  174. <Badge>{ruleEngine}</Badge>
  175. </span>
  176. </Tooltip>
  177. </li>
  178. )}
  179. {!!issue.codeVariants?.length && (
  180. <>
  181. <IssueMetaListItem>
  182. <Tooltip overlay={issue.codeVariants.join(', ')}>
  183. <span>
  184. {issue.codeVariants.length > 1
  185. ? translateWithParameters('issue.x_code_variants', issue.codeVariants.length)
  186. : translate('issue.1_code_variant')}
  187. </span>
  188. </Tooltip>
  189. </IssueMetaListItem>
  190. <SeparatorCircleIcon aria-hidden as="li" />
  191. </>
  192. )}
  193. {showComments && hasComments && (
  194. <>
  195. <IssueMetaListItem className={issueMetaListItemClassNames}>
  196. <CommentIcon aria-label={translate('issue.comment.formlink')} />
  197. {issue.comments?.length}
  198. </IssueMetaListItem>
  199. <SeparatorCircleIcon aria-hidden as="li" />
  200. </>
  201. )}
  202. {showLine && isDefined(issue.textRange) && (
  203. <>
  204. <Tooltip overlay={translate('line_number')}>
  205. <IssueMetaListItem className={issueMetaListItemClassNames}>
  206. {translateWithParameters('issue.ncloc_x.short', issue.textRange.endLine)}
  207. </IssueMetaListItem>
  208. </Tooltip>
  209. <SeparatorCircleIcon aria-hidden as="li" />
  210. </>
  211. )}
  212. {issue.effort && (
  213. <>
  214. <IssueMetaListItem className={issueMetaListItemClassNames}>
  215. {translateWithParameters('issue.x_effort', issue.effort)}
  216. </IssueMetaListItem>
  217. <SeparatorCircleIcon aria-hidden as="li" />
  218. </>
  219. )}
  220. <IssueMetaListItem className={issueMetaListItemClassNames}>
  221. <DateFromNow date={issue.creationDate} />
  222. </IssueMetaListItem>
  223. </ul>
  224. </div>
  225. );
  226. }
  227. const IssueMetaListItem = styled.li`
  228. color: ${themeColor('pageContentLight')};
  229. `;