* 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 tw from 'twin.macro';
import { themeBorder, themeColor, themeContrast } from '../helpers/theme';
interface Props<V> {
className?: string;
- components?: any;
+ components?: Parameters<typeof InputSelect>[0]['components'];
customValue?: JSX.Element;
isDisabled?: boolean;
menuIsOpen?: boolean;
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { useTheme as themeInfo } from '@emotion/react';
import classNames from 'classnames';
import { omit } from 'lodash';
classNames={{
container: () => 'sw-relative sw-inline-block sw-align-middle',
placeholder: () => 'sw-truncate sw-leading-4',
- menu: () => 'sw-z-dropdown-menu',
+ menu: () => 'sw-z-dropdown-menu sw-ml-1/2 sw-mt-2',
menuList: () => 'sw-overflow-y-auto sw-py-2 sw-max-h-[12.25rem]',
control: ({ isDisabled }) =>
classNames(
- 'sw-absolut sw-box-border sw-rounded-2 sw-overflow-hidden',
+ 'sw-box-border sw-rounded-2 sw-overflow-hidden',
isDisabled && 'sw-pointer-events-none sw-cursor-not-allowed'
),
option: ({ isDisabled }) =>
import styled from '@emotion/styled';
import { memo } from 'react';
import tw from 'twin.macro';
+import { IssueType } from '../../types';
import { IssueTypeIcon } from '../icons/IssueTypeIcon';
-export type IssueType = 'BUG' | 'VULNERABILITY' | 'CODE_SMELL' | 'SECURITY_HOTSPOT';
-
interface Props {
issuesCount: number;
mostImportantIssueType: IssueType;
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import tw from 'twin.macro';
import { themeColor, themeContrast } from '../../helpers/theme';
+import { IssueType } from '../../types';
import { BugIcon } from './BugIcon';
import { CodeSmellIcon } from './CodeSmellIcon';
import { IconProps } from './Icon';
import { SecurityFindingIcon } from './SecurityFindingIcon';
import { VulnerabilityIcon } from './VulnerabilityIcon';
-export type IssueType = 'BUG' | 'VULNERABILITY' | 'CODE_SMELL' | 'SECURITY_HOTSPOT';
export interface Props extends IconProps {
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
type: string | IssueType;
*/
export * from './charts';
+export * from './issues';
export * from './measures';
export * from './theme';
--- /dev/null
+/*
+ * 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.
+ */
+
+export type IssueType = 'BUG' | 'VULNERABILITY' | 'CODE_SMELL' | 'SECURITY_HOTSPOT';
* 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 { Badge, themeBorder, themeColor, themeContrast } from 'design-system';
import * as React from 'react';
{displayProject && (
<span title={projectName}>
{limitComponentName(issue.projectName)}
+
{displayBranchInformation && (
<>
{' - '}
)}
</>
)}
+
<SlashSeparator className="sw-mx-1" />
</span>
)}
- <span title={componentName}>{collapsePath(componentName || '')}</span>
+ <span title={componentName}>{collapsePath(componentName ?? '')}</span>
</DivStyled>
);
}
const DivStyled = styled.div`
- color: ${themeContrast('subnavigation')};
background-color: ${themeColor('subnavigation')};
+ color: ${themeContrast('breadcrumb')};
+
&:not(:last-child) {
border-bottom: ${themeBorder('default')};
}
/>
</div>
<IssueActionsBar
- className="issue-header-actions"
currentPopup={issuePopupName}
issue={issue}
onAssign={this.handleAssignement}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import {
+ StatusConfirmedIcon,
+ StatusOpenIcon,
+ StatusReopenedIcon,
+ StatusResolvedIcon,
+} from 'design-system';
import * as React from 'react';
-import { colors } from '../../app/theme';
import { Dict } from '../../types/types';
-import Icon, { IconProps } from './Icon';
+import { IconProps } from './Icon';
interface Props extends IconProps {
status: string;
}
const statusIcons: Dict<(props: IconProps) => React.ReactElement> = {
- open: OpenStatusIcon,
- confirmed: ConfirmedStatusIcon,
- reopened: ReopenedStatusIcon,
- resolved: ResolvedStatusIcon,
- closed: ClosedStatusIcon,
- to_review: OpenStatusIcon,
- in_review: ConfirmedStatusIcon,
- reviewed: ResolvedStatusIcon,
+ closed: StatusResolvedIcon,
+ confirm: StatusConfirmedIcon,
+ confirmed: StatusConfirmedIcon,
+ falsepositive: StatusResolvedIcon,
+ in_review: StatusConfirmedIcon,
+ open: StatusOpenIcon,
+ reopened: StatusReopenedIcon,
+ resolve: StatusResolvedIcon,
+ resolved: StatusResolvedIcon,
+ reviewed: StatusResolvedIcon,
+ to_review: StatusOpenIcon,
+ wontfix: StatusResolvedIcon,
};
export default function StatusIcon({ status, ...iconProps }: Props) {
const DesiredStatusIcon = statusIcons[status.toLowerCase()];
- return DesiredStatusIcon ? <DesiredStatusIcon {...iconProps} /> : null;
-}
-
-function OpenStatusIcon(iconProps: IconProps) {
- return (
- <Icon {...iconProps}>
- <path
- d="M8 3.75c-.77 0-1.482.19-2.133.57A4.25 4.25 0 0 0 4.32 5.867c-.38.65-.57 1.362-.57 2.133 0 .77.19 1.482.57 2.133.38.65.896 1.167 1.547 1.547.65.38 1.362.57 2.133.57.77 0 1.482-.19 2.133-.57a4.242 4.242 0 0 0 1.547-1.547c.38-.65.57-1.362.57-2.133 0-.77-.19-1.482-.57-2.133a4.25 4.25 0 0 0-1.547-1.547A4.153 4.153 0 0 0 8 3.75zM14 8c0 1.09-.268 2.092-.805 3.012a5.96 5.96 0 0 1-2.183 2.183A5.863 5.863 0 0 1 8 14a5.863 5.863 0 0 1-3.012-.805 5.96 5.96 0 0 1-2.183-2.183A5.863 5.863 0 0 1 2 8c0-1.09.268-2.092.805-3.012a5.96 5.96 0 0 1 2.183-2.183A5.863 5.863 0 0 1 8 2c1.09 0 2.092.268 3.012.805a5.96 5.96 0 0 1 2.183 2.183C13.732 5.908 14 6.91 14 8z"
- style={{ fill: colors.blue }}
- />
- </Icon>
- );
-}
-
-function ConfirmedStatusIcon(iconProps: IconProps) {
- return (
- <Icon {...iconProps}>
- <path
- d="M10 8c0 .552-.195 1.023-.586 1.414-.39.39-.862.586-1.414.586a1.926 1.926 0 0 1-1.414-.586A1.928 1.928 0 0 1 6 8c0-.552.195-1.023.586-1.414C6.976 6.196 7.448 6 8 6c.552 0 1.023.195 1.414.586.39.39.586.862.586 1.414zM8 3.75c-.77 0-1.482.19-2.133.57A4.25 4.25 0 0 0 4.32 5.867c-.38.65-.57 1.362-.57 2.133 0 .77.19 1.482.57 2.133.38.65.896 1.167 1.547 1.547.65.38 1.362.57 2.133.57.77 0 1.482-.19 2.133-.57a4.242 4.242 0 0 0 1.547-1.547c.38-.65.57-1.362.57-2.133 0-.77-.19-1.482-.57-2.133a4.25 4.25 0 0 0-1.547-1.547A4.153 4.153 0 0 0 8 3.75zM14 8c0 1.09-.268 2.092-.805 3.012a5.96 5.96 0 0 1-2.183 2.183A5.863 5.863 0 0 1 8 14a5.863 5.863 0 0 1-3.012-.805 5.96 5.96 0 0 1-2.183-2.183A5.863 5.863 0 0 1 2 8c0-1.09.268-2.092.805-3.012a5.96 5.96 0 0 1 2.183-2.183A5.863 5.863 0 0 1 8 2c1.09 0 2.092.268 3.012.805a5.96 5.96 0 0 1 2.183 2.183C13.732 5.908 14 6.91 14 8z"
- style={{ fill: colors.blue }}
- />
- </Icon>
- );
-}
-
-function ReopenedStatusIcon(iconProps: IconProps) {
- return (
- <Icon {...iconProps}>
- <path
- d="M8 12.25v-8.5c-.77 0-1.482.19-2.133.57A4.25 4.25 0 0 0 4.32 5.867c-.38.65-.57 1.362-.57 2.133 0 .77.19 1.482.57 2.133.38.65.896 1.167 1.547 1.547.65.38 1.362.57 2.133.57zM14 8c0 1.09-.268 2.092-.805 3.012a5.96 5.96 0 0 1-2.183 2.183A5.863 5.863 0 0 1 8 14a5.863 5.863 0 0 1-3.012-.805 5.96 5.96 0 0 1-2.183-2.183A5.863 5.863 0 0 1 2 8c0-1.09.268-2.092.805-3.012a5.96 5.96 0 0 1 2.183-2.183A5.863 5.863 0 0 1 8 2c1.09 0 2.092.268 3.012.805a5.96 5.96 0 0 1 2.183 2.183C13.732 5.908 14 6.91 14 8z"
- style={{ fill: colors.blue }}
- />
- </Icon>
- );
-}
-function ResolvedStatusIcon(iconProps: IconProps) {
- return (
- <Icon {...iconProps}>
- <path
- d="M12.03 6.734a.49.49 0 0 0-.14-.36l-.71-.702a.48.48 0 0 0-.352-.15.474.474 0 0 0-.35.15l-3.19 3.18-1.765-1.766a.479.479 0 0 0-.35-.15.479.479 0 0 0-.353.15l-.71.703a.482.482 0 0 0-.14.358c0 .14.046.258.14.352l2.828 2.828c.098.1.216.15.35.15.142 0 .26-.05.36-.15l4.243-4.242a.475.475 0 0 0 .14-.352l-.001.001zM14 8c0 1.09-.268 2.092-.805 3.012a5.96 5.96 0 0 1-2.183 2.183A5.863 5.863 0 0 1 8 14a5.863 5.863 0 0 1-3.012-.805 5.96 5.96 0 0 1-2.183-2.183A5.863 5.863 0 0 1 2 8c0-1.09.268-2.092.805-3.012a5.96 5.96 0 0 1 2.183-2.183A5.863 5.863 0 0 1 8 2c1.09 0 2.092.268 3.012.805a5.96 5.96 0 0 1 2.183 2.183C13.732 5.908 14 6.91 14 8z"
- style={{ fill: colors.baseFontColor }}
- />
- </Icon>
- );
-}
-
-function ClosedStatusIcon(iconProps: IconProps) {
- return (
- <Icon {...iconProps}>
- <path
- d="M14 8c0 1.09-.268 2.092-.805 3.012a5.96 5.96 0 0 1-2.183 2.183A5.863 5.863 0 0 1 8 14a5.863 5.863 0 0 1-3.012-.805 5.96 5.96 0 0 1-2.183-2.183A5.863 5.863 0 0 1 2 8c0-1.09.268-2.092.805-3.012a5.96 5.96 0 0 1 2.183-2.183A5.863 5.863 0 0 1 8 2c1.09 0 2.092.268 3.012.805a5.96 5.96 0 0 1 2.183 2.183C13.732 5.908 14 6.91 14 8z"
- style={{ fill: colors.baseFontColor }}
- />
- </Icon>
- );
+ return DesiredStatusIcon ? <DesiredStatusIcon {...iconProps} /> : null;
}
margin-left: var(--gridSize);
}
-.issue-header-actions .issue-meta + .issue-meta {
- margin-left: calc(var(--gridSize) * 2);
-}
-
.issue-meta-label {
display: inline-block;
vertical-align: top;
* 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';
showCommentsInPopup,
showLine,
} = props;
+
const [commentState, setCommentState] = React.useState<State>({
commentAutoTriggered: false,
commentPlaceholder: '',
const newIssue = { ...issue, [property]: value };
updateIssue(onChange, apiCall({ issue: issue.key, [property]: value }), issue, newIssue);
}
+
togglePopup(popup, false);
};
- const toggleComment = (open: boolean | undefined, placeholder = '', autoTriggered = 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)
};
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'
<li>
<IssueType canSetType={canSetType} issue={issue} setIssueProperty={setIssueProperty} />
</li>
+
<li>
<IssueSeverity
isOpen={currentPopup === 'set-severity'}
setIssueProperty={setIssueProperty}
/>
</li>
+
<li>
<IssueTransition
isOpen={currentPopup === 'transition'}
onChange={handleTransition}
/>
</li>
+
<li>
<IssueAssign
isOpen={currentPopup === 'assign'}
/>
</li>
</ul>
+
{(canComment || showCommentsInPopup) && (
<IssueCommentAction
commentAutoTriggered={commentState.commentAutoTriggered}
<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')}>
{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>
currentPopup?: boolean;
issueKey: string;
onChange: (issue: Issue) => void;
- toggleComment: (open?: boolean, placeholder?: string, autoTriggered?: boolean) => void;
+ toggleComment: (open: boolean, placeholder?: string, autoTriggered?: boolean) => void;
comments?: IssueComment[];
showCommentsInPopup?: boolean;
}
updateIssue(this.props.onChange, deleteIssueComment({ comment }));
};
- handleCommentClick = () => {
- this.props.toggleComment();
- };
-
handleClose = () => {
this.props.toggleComment(false);
};
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { DiscreetSelect } from 'design-system';
import * as React from 'react';
import { setIssueSeverity } from '../../../api/issues';
+import { SEVERITIES } from '../../../helpers/constants';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { IssueResponse } from '../../../types/issues';
+import { IssueResponse, IssueSeverity as IssueSeverityType } from '../../../types/issues';
import { Issue, RawQuery } from '../../../types/types';
-import SeverityIcon from '../../icons/SeverityIcon';
+import IssueSeverityIcon from '../../icon-mappers/IssueSeverityIcon';
interface Props {
canSetSeverity: boolean;
this.toggleSetSeverity(false);
};
- toggleSetSeverity = (open?: boolean) => {
+ toggleSetSeverity = (open: boolean) => {
this.props.togglePopup('set-severity', open);
};
render() {
const { issue } = this.props;
- const SEVERITY = ['BLOCKER', 'CRITICAL', 'MAJOR', 'MINOR', 'INFO'];
- const typesOptions = SEVERITY.map((severity) => ({
+
+ const typesOptions = SEVERITIES.map((severity) => ({
label: translate('severity', severity),
value: severity,
- Icon: <SeverityIcon severity={severity} aria-hidden />,
+ Icon: <IssueSeverityIcon severity={severity} aria-hidden />,
}));
if (this.props.canSetSeverity) {
return (
<span className="sw-flex sw-items-center sw-gap-1">
- <SeverityIcon className="little-spacer-right" severity={issue.severity} aria-hidden />
+ <IssueSeverityIcon severity={issue.severity as IssueSeverityType} aria-hidden />
+
{translate('severity', issue.severity)}
</span>
);
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { DiscreetSelect } from 'design-system';
import * as React from 'react';
import { GroupBase, OptionProps, components } from 'react-select';
togglePopup: (popup: string, show?: boolean) => void;
}
+function SingleValueFactory(issue: Props['issue']) {
+ return function SingleValue<
+ V,
+ Option extends LabelValueSelectOption<V>,
+ IsMulti extends boolean = false,
+ Group extends GroupBase<Option> = GroupBase<Option>
+ >(props: OptionProps<Option, IsMulti, Group>) {
+ return (
+ <components.SingleValue {...props}>
+ <StatusHelper
+ className="sw-flex sw-items-center"
+ resolution={issue.resolution}
+ status={issue.status}
+ />
+ </components.SingleValue>
+ );
+ };
+}
+
export default class IssueTransition extends React.PureComponent<Props> {
setTransition = ({ value }: { value: string }) => {
updateIssue(
this.props.onChange,
setIssueTransition({ issue: this.props.issue.key, transition: value })
);
+
this.toggleSetTransition(false);
};
- toggleSetTransition = (open?: boolean) => {
+ toggleSetTransition = (open: boolean) => {
this.props.togglePopup('transition', open);
};
)}
size="medium"
className="it__issue-transition"
- components={{
- SingleValue: <
- V,
- Option extends LabelValueSelectOption<V>,
- IsMulti extends boolean = false,
- Group extends GroupBase<Option> = GroupBase<Option>
- >(
- props: OptionProps<Option, IsMulti, Group>
- ) => {
- return (
- <components.SingleValue {...props}>
- <StatusHelper
- className="sw-flex sw-items-center"
- resolution={issue.resolution}
- status={issue.status}
- />
- </components.SingleValue>
- );
- },
- }}
+ components={{ SingleValue: SingleValueFactory(issue) }}
menuIsOpen={this.props.isOpen && this.props.hasTransitions}
options={transitions}
setValue={this.setTransition}
}
const resolution = issue.resolution && ` (${translate('issue.resolution', issue.resolution)})`;
+
return (
<span className="sw-flex sw-items-center sw-gap-1">
<StatusIcon status={issue.status} />
+
{translate('issue.status', issue.status)}
+
{resolution}
</span>
);
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { DiscreetSelect } from 'design-system';
import * as React from 'react';
import { setIssueType } from '../../../api/issues';
-import IssueTypeIcon from '../../../components/icons/IssueTypeIcon';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { IssueResponse } from '../../../types/issues';
import { Issue, RawQuery } from '../../../types/types';
+import IssueTypeIcon from '../../icon-mappers/IssueTypeIcon';
interface Props {
canSetType: boolean;
render() {
const { issue } = this.props;
const TYPES = ['BUG', 'VULNERABILITY', 'CODE_SMELL'];
+
const typesOptions = TYPES.map((type) => ({
label: translate('issue.type', type),
value: type,
- Icon: <IssueTypeIcon query={type} />,
+ Icon: <IssueTypeIcon type={type} />,
}));
+
if (this.props.canSetType) {
return (
<DiscreetSelect
return (
<span className="sw-flex sw-items-center sw-gap-1">
- <IssueTypeIcon query={issue.type} />
+ <IssueTypeIcon type={issue.type} />
{translate('issue.type', issue.type)}
</span>
);
* 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 { Checkbox, themeBorder, themeColor } from 'design-system';
>
<div className="sw-flex sw-w-full sw-px-2 sw-gap-4">
{hasCheckbox && (
- <Checkbox
- checked={checked ?? false}
- onCheck={this.handleCheck}
- label={translateWithParameters('issues.action_select.label', issue.message)}
- title={translate('issues.action_select')}
- />
+ <span className="sw-mt-1/2 sw-self-start">
+ <Checkbox
+ checked={checked ?? false}
+ onCheck={this.handleCheck}
+ label={translateWithParameters('issues.action_select.label', issue.message)}
+ title={translate('issues.action_select')}
+ />
+ </span>
)}
+
<div className="sw-flex sw-flex-col sw-grow sw-gap-2">
<IssueTitleBar
currentPopup={currentPopup}
onChange={this.props.onChange}
togglePopup={this.props.togglePopup}
/>
+
<IssueActionsBar
currentPopup={currentPopup}
issue={issue}