import {Component, Fragment} from 'react'; import styled from '@emotion/styled'; import moment from 'moment'; import {openInviteMembersModal} from 'sentry/actionCreators/modal'; import Alert from 'sentry/components/alert'; import ActorAvatar from 'sentry/components/avatar/actorAvatar'; import Button from 'sentry/components/button'; import CommitLink from 'sentry/components/commitLink'; import {Divider, Hovercard} from 'sentry/components/hovercard'; import Link from 'sentry/components/links/link'; import Version from 'sentry/components/version'; import {IconCommit} from 'sentry/icons'; import {t, tct} from 'sentry/locale'; import space from 'sentry/styles/space'; import type {Actor, Commit, Organization, Release} from 'sentry/types'; import {defined} from 'sentry/utils'; import theme from 'sentry/utils/theme'; type Props = { /** * The suggested actor. */ actor: Actor; /** * Children are required, as they are passed to the hovercard component, without it, * we will not be able to trigger any hovercard actions */ children: React.ReactNode; organization: Organization; /** * The list of commits the actor is suggested for. May be left blank if the * actor is not suggested for commits. */ commits?: Commit[]; /** * Used to pre-select release project */ projectId?: string; release?: Release; /** * The list of ownership rules the actor is suggested for. May be left blank * if the actor is not suggested based on ownership rules. */ rules?: any[] | null; }; type State = { commitsExpanded: boolean; rulesExpanded: boolean; }; class SuggestedOwnerHovercard extends Component { state: State = { commitsExpanded: false, rulesExpanded: false, }; render() { const {organization, actor, commits, rules, release, projectId, ...props} = this.props; const {commitsExpanded, rulesExpanded} = this.state; const modalData = { initialData: [ { emails: actor.email ? new Set([actor.email]) : new Set([]), }, ], source: 'suggested_assignees', }; return ( {actor.name || actor.email} {actor.id === undefined && ( {tct( 'The email [actorEmail] is not a member of your organization. [inviteUser:Invite] them or link additional emails in [accountSettings:account settings].', { actorEmail: {actor.email}, accountSettings: , inviteUser: openInviteMembersModal(modalData)} />, } )} )} } body={ {commits !== undefined && !release && (
{t('Commits')}
{commits .slice(0, commitsExpanded ? commits.length : 3) .map(({message, dateCreated}, i) => ( ))}
{commits.length > 3 && !commitsExpanded ? ( this.setState({commitsExpanded: true})} > {t('View more')} ) : null}
)} {commits !== undefined && release && (
{t('Suspect Release')}
{tct('[actor] [verb] [commits] in [release]', { actor: actor.name, verb: commits.length > 1 ? t('made') : t('last committed'), commits: commits.length > 1 ? ( // Link to release commits {t('%s commits', commits.length)} ) : ( ), release: ( ), })}
)} {defined(rules) && (
{t('Matching Ownership Rules')}
{rules .slice(0, rulesExpanded ? rules.length : 3) .map(([type, matched], i) => ( {matched} ))}
{rules.length > 3 && !rulesExpanded ? ( this.setState({rulesExpanded: true})} > {t('View more')} ) : null}
)}
} {...props} /> ); } } const tagColors = { url: theme.green200, path: theme.purple300, tag: theme.blue300, codeowners: theme.pink300, release: theme.pink200, }; const StyledHovercard = styled(Hovercard)` width: 400px; `; const CommitIcon = styled(IconCommit)` margin-right: ${space(0.5)}; flex-shrink: 0; `; const CommitMessage = styled(({message = '', date, ...props}) => (
{message.split('\n')[0]}
))` color: ${p => p.theme.textColor}; font-size: ${p => p.theme.fontSizeExtraSmall}; margin-top: ${space(0.25)}; hyphens: auto; `; const CommitDate = styled(({date, ...props}) => (
{moment(date).fromNow()}
))` margin-top: ${space(0.5)}; color: ${p => p.theme.gray300}; `; const CommitReasonItem = styled('div')` display: flex; align-items: flex-start; gap: ${space(1)}; `; const RuleReasonItem = styled('div')` display: flex; align-items: flex-start; gap: ${space(1)}; `; const OwnershipTag = styled(({tagType, ...props}) =>
{tagType}
)` background: ${p => tagColors[p.tagType.indexOf('tags') === -1 ? p.tagType : 'tag']}; color: ${p => p.theme.white}; font-size: ${p => p.theme.fontSizeExtraSmall}; padding: ${space(0.25)} ${space(0.5)}; margin: ${space(0.25)} ${space(0.5)} ${space(0.25)} 0; border-radius: 2px; font-weight: bold; text-align: center; `; const ViewMoreButton = styled(Button)` border: none; color: ${p => p.theme.gray300}; font-size: ${p => p.theme.fontSizeExtraSmall}; padding: ${space(0.25)} ${space(0.5)}; margin: ${space(1)} ${space(0.25)} ${space(0.25)} 0; width: 100%; min-width: 34px; `; const OwnershipValue = styled('code')` word-break: break-all; font-size: ${p => p.theme.fontSizeExtraSmall}; margin-top: ${space(0.25)}; `; const ReleaseValue = styled('div')` font-size: ${p => p.theme.fontSizeSmall}; margin-top: ${space(0.5)}; `; const EmailAlert = styled(Alert)` margin: 10px -13px -13px; border-radius: 0; border-color: #ece0b0; font-size: ${p => p.theme.fontSizeSmall}; font-weight: normal; box-shadow: none; `; const HovercardHeader = styled('div')` display: flex; align-items: center; gap: ${space(1)}; `; const HovercardBody = styled('div')` margin-top: -${space(2)}; `; export default SuggestedOwnerHovercard;