123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- /*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
- import styled from '@emotion/styled';
- import classNames from 'classnames';
- import { Badge, CommentIcon, SeparatorCircleIcon, themeColor } from 'design-system';
- import * as React from 'react';
- import { translate, translateWithParameters } from '../../../helpers/l10n';
- import { isDefined } from '../../../helpers/types';
- import {
- IssueActions,
- IssueResolution,
- IssueResponse,
- IssueType as IssueTypeEnum,
- } from '../../../types/issues';
- import { RuleStatus } from '../../../types/rules';
- import { Issue, RawQuery } from '../../../types/types';
- import Tooltip from '../../controls/Tooltip';
- import DateFromNow from '../../intl/DateFromNow';
- import { WorkspaceContext } from '../../workspace/context';
- import { updateIssue } from '../actions';
- import IssueAssign from './IssueAssign';
- import IssueBadges from './IssueBadges';
- import IssueCommentAction from './IssueCommentAction';
- import IssueSeverity from './IssueSeverity';
- import IssueTransition from './IssueTransition';
- import IssueType from './IssueType';
-
- interface Props {
- issue: Issue;
- currentPopup?: string;
- onAssign: (login: string) => void;
- onChange: (issue: Issue) => void;
- togglePopup: (popup: string, show?: boolean) => void;
- className?: string;
- showComments?: boolean;
- showLine?: boolean;
- }
-
- interface State {
- commentAutoTriggered: boolean;
- commentPlaceholder: string;
- }
-
- export default function IssueActionsBar(props: Props) {
- const {
- issue,
- currentPopup,
- onAssign,
- onChange,
- togglePopup,
- className,
- showComments,
- showLine,
- } = props;
-
- const [commentState, setCommentState] = React.useState<State>({
- commentAutoTriggered: false,
- commentPlaceholder: '',
- });
-
- const setIssueProperty = (
- property: keyof Issue,
- popup: string,
- apiCall: (query: RawQuery) => Promise<IssueResponse>,
- value: string
- ) => {
- if (issue[property] !== value) {
- const newIssue = { ...issue, [property]: value };
- updateIssue(onChange, apiCall({ issue: issue.key, [property]: value }), issue, newIssue);
- }
-
- togglePopup(popup, false);
- };
-
- const toggleComment = (open: boolean, placeholder = '', autoTriggered = false) => {
- setCommentState({
- commentPlaceholder: placeholder,
- commentAutoTriggered: autoTriggered,
- });
-
- togglePopup('comment', open);
- };
-
- const handleTransition = (issue: Issue) => {
- onChange(issue);
-
- if (
- issue.resolution === IssueResolution.FalsePositive ||
- (issue.resolution === IssueResolution.WontFix && issue.type !== IssueTypeEnum.SecurityHotspot)
- ) {
- toggleComment(true, translate('issue.comment.explain_why'), true);
- }
- };
-
- const { externalRulesRepoNames } = React.useContext(WorkspaceContext);
-
- const ruleEngine =
- (issue.externalRuleEngine && externalRulesRepoNames[issue.externalRuleEngine]) ||
- issue.externalRuleEngine;
-
- const canAssign = issue.actions.includes(IssueActions.Assign);
- const canComment = issue.actions.includes(IssueActions.Comment);
- const canSetSeverity = issue.actions.includes(IssueActions.SetSeverity);
- const canSetType = issue.actions.includes(IssueActions.SetType);
- const hasTransitions = issue.transitions.length > 0;
- const hasComments = !!issue.comments?.length;
-
- const issueMetaListItemClassNames = classNames(
- className,
- 'sw-body-sm sw-overflow-hidden sw-whitespace-nowrap sw-max-w-abs-150'
- );
-
- return (
- <div
- className={classNames(className, 'sw-flex sw-flex-wrap sw-items-center sw-justify-between')}
- >
- <ul className="it__issue-header-actions sw-flex sw-items-center sw-gap-3 sw-body-sm">
- <li>
- <IssueType canSetType={canSetType} issue={issue} setIssueProperty={setIssueProperty} />
- </li>
-
- <li>
- <IssueTransition
- isOpen={currentPopup === 'transition'}
- togglePopup={togglePopup}
- hasTransitions={hasTransitions}
- issue={issue}
- onChange={handleTransition}
- />
- </li>
-
- <li>
- <IssueSeverity
- isOpen={currentPopup === 'set-severity'}
- togglePopup={togglePopup}
- canSetSeverity={canSetSeverity}
- issue={issue}
- setIssueProperty={setIssueProperty}
- />
- </li>
-
- <li>
- <IssueAssign
- isOpen={currentPopup === 'assign'}
- togglePopup={togglePopup}
- canAssign={canAssign}
- issue={issue}
- onAssign={onAssign}
- />
- </li>
- </ul>
- {canComment && (
- <IssueCommentAction
- commentAutoTriggered={commentState.commentAutoTriggered}
- commentPlaceholder={commentState.commentPlaceholder}
- currentPopup={currentPopup === 'comment'}
- issueKey={issue.key}
- onChange={onChange}
- toggleComment={toggleComment}
- />
- )}
-
- <ul className="sw-flex sw-items-center sw-gap-2 sw-body-sm">
- <li className={issueMetaListItemClassNames}>
- <IssueBadges
- quickFixAvailable={issue.quickFixAvailable}
- ruleStatus={issue.ruleStatus as RuleStatus | undefined}
- />
- </li>
-
- {ruleEngine && (
- <li className={issueMetaListItemClassNames}>
- <Tooltip
- overlay={translateWithParameters('issue.from_external_rule_engine', ruleEngine)}
- >
- <span>
- <Badge>{ruleEngine}</Badge>
- </span>
- </Tooltip>
- </li>
- )}
-
- {!!issue.codeVariants?.length && (
- <>
- <IssueMetaListItem>
- <Tooltip overlay={issue.codeVariants.join(', ')}>
- <span>
- {issue.codeVariants.length > 1
- ? translateWithParameters('issue.x_code_variants', issue.codeVariants.length)
- : translate('issue.1_code_variant')}
- </span>
- </Tooltip>
- </IssueMetaListItem>
- <SeparatorCircleIcon aria-hidden as="li" />
- </>
- )}
-
- {showComments && hasComments && (
- <>
- <IssueMetaListItem className={issueMetaListItemClassNames}>
- <CommentIcon aria-label={translate('issue.comment.formlink')} />
- {issue.comments?.length}
- </IssueMetaListItem>
-
- <SeparatorCircleIcon aria-hidden as="li" />
- </>
- )}
-
- {showLine && isDefined(issue.textRange) && (
- <>
- <Tooltip overlay={translate('line_number')}>
- <IssueMetaListItem className={issueMetaListItemClassNames}>
- {translateWithParameters('issue.ncloc_x.short', issue.textRange.endLine)}
- </IssueMetaListItem>
- </Tooltip>
-
- <SeparatorCircleIcon aria-hidden as="li" />
- </>
- )}
-
- {issue.effort && (
- <>
- <IssueMetaListItem className={issueMetaListItemClassNames}>
- {translateWithParameters('issue.x_effort', issue.effort)}
- </IssueMetaListItem>
-
- <SeparatorCircleIcon aria-hidden as="li" />
- </>
- )}
-
- <IssueMetaListItem className={issueMetaListItemClassNames}>
- <DateFromNow date={issue.creationDate} />
- </IssueMetaListItem>
- </ul>
- </div>
- );
- }
-
- const IssueMetaListItem = styled.li`
- color: ${themeColor('pageContentLight')};
- `;
|