123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181 |
- import {Fragment} from 'react';
- import styled from '@emotion/styled';
- import {
- AssigneeSelectorDropdown,
- AssigneeSelectorDropdownProps,
- SuggestedAssignee,
- } from 'sentry/components/assigneeSelectorDropdown';
- import ActorAvatar from 'sentry/components/avatar/actorAvatar';
- import SuggestedAvatarStack from 'sentry/components/avatar/suggestedAvatarStack';
- import DropdownBubble from 'sentry/components/dropdownBubble';
- import ExternalLink from 'sentry/components/links/externalLink';
- import LoadingIndicator from 'sentry/components/loadingIndicator';
- import Tooltip from 'sentry/components/tooltip';
- import {IconChevron, IconUser} from 'sentry/icons';
- import {t, tct, tn} from 'sentry/locale';
- import space from 'sentry/styles/space';
- import type {Actor, SuggestedOwnerReason} from 'sentry/types';
- interface AssigneeSelectorProps extends Omit<AssigneeSelectorDropdownProps, 'children'> {
- noDropdown?: boolean;
- }
- function AssigneeSelector({noDropdown, ...props}: AssigneeSelectorProps) {
- function getActorElement(
- assignedTo?: Actor,
- suggestedActors: SuggestedAssignee[] = []
- ) {
- const suggestedReasons: Record<SuggestedOwnerReason, React.ReactNode> = {
- suspectCommit: tct('Based on [commit:commit data]', {
- commit: (
- <TooltipSubExternalLink href="https://docs.sentry.io/product/sentry-basics/integrate-frontend/configure-scms/" />
- ),
- }),
- releaseCommit: '',
- ownershipRule: t('Matching Issue Owners Rule'),
- codeowners: t('Matching Codeowners Rule'),
- };
- const assignedToSuggestion = suggestedActors.find(
- actor => actor.id === assignedTo?.id
- );
- return assignedTo ? (
- <ActorAvatar
- actor={assignedTo}
- className="avatar"
- size={24}
- tooltip={
- <TooltipWrapper>
- {tct('Assigned to [name]', {
- name: assignedTo.type === 'team' ? `#${assignedTo.name}` : assignedTo.name,
- })}
- {assignedToSuggestion &&
- suggestedReasons[assignedToSuggestion.suggestedReason] && (
- <TooltipSubtext>
- {suggestedReasons[assignedToSuggestion.suggestedReason]}
- </TooltipSubtext>
- )}
- </TooltipWrapper>
- }
- />
- ) : suggestedActors && suggestedActors.length > 0 ? (
- <SuggestedAvatarStack
- size={28}
- owners={suggestedActors}
- tooltipOptions={{isHoverable: true}}
- tooltip={
- <TooltipWrapper>
- <div>
- {tct('Suggestion: [name]', {
- name:
- suggestedActors[0].type === 'team'
- ? `#${suggestedActors[0].name}`
- : suggestedActors[0].name,
- })}
- {suggestedActors.length > 1 &&
- tn(' + %s other', ' + %s others', suggestedActors.length - 1)}
- </div>
- <TooltipSubtext>
- {suggestedReasons[suggestedActors[0].suggestedReason]}
- </TooltipSubtext>
- </TooltipWrapper>
- }
- />
- ) : (
- <Tooltip
- isHoverable
- skipWrapper
- title={
- <TooltipWrapper>
- <div>{t('Unassigned')}</div>
- <TooltipSubtext>
- {tct(
- 'You can auto-assign issues by adding [issueOwners:Issue Owner rules].',
- {
- issueOwners: (
- <TooltipSubExternalLink href="https://docs.sentry.io/product/error-monitoring/issue-owners/" />
- ),
- }
- )}
- </TooltipSubtext>
- </TooltipWrapper>
- }
- >
- <StyledIconUser data-test-id="unassigned" size="20px" color="gray400" />
- </Tooltip>
- );
- }
- return (
- <AssigneeWrapper>
- <AssigneeSelectorDropdown {...props}>
- {({loading, isOpen, assignedTo, getActorProps, suggestedAssignees}) => {
- const avatarElement = getActorElement(assignedTo, suggestedAssignees);
- return (
- <Fragment>
- {loading && (
- <LoadingIndicator
- mini
- style={{height: '24px', margin: 0, marginRight: 11}}
- />
- )}
- {!loading && !noDropdown && (
- <DropdownButton data-test-id="assignee-selector" {...getActorProps({})}>
- {avatarElement}
- <StyledChevron direction={isOpen ? 'up' : 'down'} size="xs" />
- </DropdownButton>
- )}
- {!loading && noDropdown && avatarElement}
- </Fragment>
- );
- }}
- </AssigneeSelectorDropdown>
- </AssigneeWrapper>
- );
- }
- export default AssigneeSelector;
- const AssigneeWrapper = styled('div')`
- display: flex;
- justify-content: flex-end;
- /* manually align menu underneath dropdown caret */
- ${DropdownBubble} {
- right: -14px;
- }
- `;
- const StyledIconUser = styled(IconUser)`
- /* We need this to center with Avatar */
- margin-right: 2px;
- `;
- const StyledChevron = styled(IconChevron)`
- margin-left: ${space(1)};
- `;
- const DropdownButton = styled('div')`
- display: flex;
- align-items: center;
- font-size: 20px;
- `;
- const TooltipWrapper = styled('div')`
- text-align: left;
- `;
- const TooltipSubtext = styled('div')`
- color: ${p => p.theme.subText};
- `;
- const TooltipSubExternalLink = styled(ExternalLink)`
- color: ${p => p.theme.subText};
- text-decoration: underline;
- :hover {
- color: ${p => p.theme.subText};
- }
- `;
|