Skip to content

Commit 20de7d4

Browse files
authored
feat(releases): Split release commit from shared component (#78538)
1 parent 9c8046b commit 20de7d4

File tree

2 files changed

+196
-2
lines changed

2 files changed

+196
-2
lines changed

static/app/views/releases/detail/commitsAndFiles/commits.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {Fragment} from 'react';
22
import styled from '@emotion/styled';
33
import type {Location} from 'history';
44

5-
import {CommitRow} from 'sentry/components/commitRow';
65
import * as Layout from 'sentry/components/layouts/thirds';
76
import LoadingError from 'sentry/components/loadingError';
87
import LoadingIndicator from 'sentry/components/loadingIndicator';
@@ -22,6 +21,7 @@ import {useLocation} from 'sentry/utils/useLocation';
2221
import useOrganization from 'sentry/utils/useOrganization';
2322
import {useParams} from 'sentry/utils/useParams';
2423
import {formatVersion} from 'sentry/utils/versions/formatVersion';
24+
import {ReleaseCommit} from 'sentry/views/releases/detail/commitsAndFiles/releaseCommit';
2525

2626
import {getCommitsByRepository, getQuery, getReposToRender} from '../utils';
2727

@@ -93,7 +93,7 @@ function Commits({activeReleaseRepo, releaseRepos, projectSlug}: CommitsProps) {
9393
<PanelHeader>{activeRepoName}</PanelHeader>
9494
<PanelBody>
9595
{commitsByRepository[activeRepoName]?.map(commit => (
96-
<CommitRow key={commit.id} commit={commit} />
96+
<ReleaseCommit key={commit.id} commit={commit} />
9797
))}
9898
</PanelBody>
9999
</Panel>
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import {useCallback} from 'react';
2+
import styled from '@emotion/styled';
3+
import * as Sentry from '@sentry/react';
4+
5+
import {openInviteMembersModal} from 'sentry/actionCreators/modal';
6+
import UserAvatar from 'sentry/components/avatar/userAvatar';
7+
import {LinkButton} from 'sentry/components/button';
8+
import CommitLink from 'sentry/components/commitLink';
9+
import Link from 'sentry/components/links/link';
10+
import PanelItem from 'sentry/components/panels/panelItem';
11+
import TextOverflow from 'sentry/components/textOverflow';
12+
import TimeSince from 'sentry/components/timeSince';
13+
import {Tooltip} from 'sentry/components/tooltip';
14+
import {IconQuestion} from 'sentry/icons';
15+
import {t, tct} from 'sentry/locale';
16+
import {space} from 'sentry/styles/space';
17+
import type {Commit} from 'sentry/types/integrations';
18+
import {useUser} from 'sentry/utils/useUser';
19+
20+
function formatCommitMessage(message: string | null) {
21+
if (!message) {
22+
return t('No message provided');
23+
}
24+
25+
return message.split(/\n/)[0];
26+
}
27+
28+
export interface ReleaseCommitProps {
29+
commit: Commit;
30+
}
31+
32+
export function ReleaseCommit({commit}: ReleaseCommitProps) {
33+
const user = useUser();
34+
35+
const handleInviteClick = useCallback(
36+
(event: React.MouseEvent<HTMLAnchorElement>) => {
37+
// Prevent link behavior
38+
event?.preventDefault();
39+
40+
if (!commit.author?.email) {
41+
Sentry.captureException(
42+
new Error(`Commit author has no email or id, invite flow is broken.`)
43+
);
44+
return;
45+
}
46+
47+
openInviteMembersModal({
48+
initialData: [
49+
{
50+
emails: new Set([commit.author.email]),
51+
},
52+
],
53+
source: 'suspect_commit',
54+
});
55+
},
56+
[commit.author]
57+
);
58+
59+
const isUser = user?.id === commit.author?.id;
60+
61+
return (
62+
<StyledPanelItem key={commit.id} data-test-id="commit-row">
63+
<CommitContent>
64+
<Message>{formatCommitMessage(commit.message)}</Message>
65+
<MetaWrapper>
66+
<UserAvatar size={16} user={commit.author} />
67+
<Meta>
68+
<Tooltip
69+
title={tct(
70+
'The email [actorEmail] is not a member of your organization. [inviteUser:Invite] them or link additional emails in [accountSettings:account settings].',
71+
{
72+
actorEmail: <BoldEmail>{commit.author?.email}</BoldEmail>,
73+
accountSettings: <StyledLink to="/settings/account/emails/" />,
74+
inviteUser: (
75+
<StyledLink
76+
to=""
77+
onClick={handleInviteClick}
78+
aria-label={t('Invite user')}
79+
/>
80+
),
81+
}
82+
)}
83+
disabled={!commit.author || commit.author.id !== undefined}
84+
overlayStyle={{maxWidth: '350px'}}
85+
skipWrapper
86+
isHoverable
87+
>
88+
<AuthorWrapper>
89+
{isUser ? t('You') : commit.author?.name ?? t('Unknown author')}
90+
{commit.author && commit.author.id === undefined && (
91+
<IconQuestion size="xs" />
92+
)}
93+
</AuthorWrapper>
94+
</Tooltip>
95+
{tct(' committed [commitLink] ', {
96+
commitLink: (
97+
<CommitLink
98+
inline
99+
showIcon={false}
100+
commitId={commit.id}
101+
repository={commit.repository}
102+
/>
103+
),
104+
})}
105+
<TimeSince date={commit.dateCreated} tooltipUnderlineColor="background" />
106+
</Meta>
107+
</MetaWrapper>
108+
</CommitContent>
109+
110+
{commit.pullRequest?.externalUrl && (
111+
<LinkButton external href={commit.pullRequest.externalUrl}>
112+
{t('View Pull Request')}
113+
</LinkButton>
114+
)}
115+
</StyledPanelItem>
116+
);
117+
}
118+
119+
const StyledPanelItem = styled(PanelItem)`
120+
display: flex;
121+
align-items: center;
122+
justify-content: space-between;
123+
gap: ${space(2)};
124+
`;
125+
126+
const BoldEmail = styled('strong')`
127+
font-weight: bold;
128+
word-break: break-all;
129+
`;
130+
131+
const StyledLink = styled(Link)`
132+
color: ${p => p.theme.textColor};
133+
border-bottom: 1px dotted ${p => p.theme.textColor};
134+
135+
&:hover {
136+
color: ${p => p.theme.textColor};
137+
}
138+
`;
139+
140+
const Message = styled(TextOverflow)`
141+
font-size: ${p => p.theme.fontSizeLarge};
142+
line-height: 1.2;
143+
`;
144+
145+
const Meta = styled(TextOverflow)`
146+
line-height: 1.5;
147+
margin: 0;
148+
color: ${p => p.theme.subText};
149+
150+
a {
151+
color: ${p => p.theme.subText};
152+
text-decoration: underline;
153+
text-decoration-style: dotted;
154+
}
155+
156+
a:hover {
157+
color: ${p => p.theme.textColor};
158+
}
159+
`;
160+
161+
const CommitContent = styled('div')`
162+
display: flex;
163+
flex-direction: column;
164+
gap: ${space(0.25)};
165+
${p => p.theme.overflowEllipsis};
166+
`;
167+
168+
const MetaWrapper = styled('div')`
169+
display: flex;
170+
align-items: center;
171+
gap: ${space(0.5)};
172+
color: ${p => p.theme.subText};
173+
font-size: ${p => p.theme.fontSizeMedium};
174+
line-height: 1.2;
175+
`;
176+
177+
const AuthorWrapper = styled('span')`
178+
display: inline-flex;
179+
align-items: center;
180+
gap: ${space(0.25)};
181+
color: ${p => p.theme.subText};
182+
183+
& svg {
184+
transition: 120ms opacity;
185+
opacity: 0.6;
186+
}
187+
188+
&:has(svg):hover {
189+
color: ${p => p.theme.textColor};
190+
& svg {
191+
opacity: 1;
192+
}
193+
}
194+
`;

0 commit comments

Comments
 (0)