shareModal.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import {Fragment, useRef} from 'react';
  2. import styled from '@emotion/styled';
  3. import type {ModalRenderProps} from 'sentry/actionCreators/modal';
  4. import AutoSelectText from 'sentry/components/autoSelectText';
  5. import {Button} from 'sentry/components/button';
  6. import ButtonBar from 'sentry/components/buttonBar';
  7. import Checkbox from 'sentry/components/checkbox';
  8. import {t} from 'sentry/locale';
  9. import GroupStore from 'sentry/stores/groupStore';
  10. import {useLegacyStore} from 'sentry/stores/useLegacyStore';
  11. import {space} from 'sentry/styles/space';
  12. import type {Event} from 'sentry/types/event';
  13. import type {Group} from 'sentry/types/group';
  14. import type {Organization} from 'sentry/types/organization';
  15. import {getAnalyticsDataForEvent, getAnalyticsDataForGroup} from 'sentry/utils/events';
  16. import normalizeUrl from 'sentry/utils/url/normalizeUrl';
  17. import useCopyToClipboard from 'sentry/utils/useCopyToClipboard';
  18. import {useLocalStorageState} from 'sentry/utils/useLocalStorageState';
  19. interface ShareIssueModalProps extends ModalRenderProps {
  20. event: Event | null;
  21. groupId: string;
  22. organization: Organization;
  23. }
  24. type UrlRef = React.ElementRef<typeof AutoSelectText>;
  25. export function getShareUrl(group: Group) {
  26. const path = `/share/issue/${group.shareId}/`;
  27. const {host, protocol} = window.location;
  28. return `${protocol}//${host}${path}`;
  29. }
  30. export default function ShareIssueModal({
  31. Header,
  32. Body,
  33. organization,
  34. groupId,
  35. closeModal,
  36. event,
  37. }: ShareIssueModalProps) {
  38. const [includeEventId, setIncludeEventId] = useLocalStorageState(
  39. 'issue-details-share-event-id',
  40. true
  41. );
  42. const urlRef = useRef<UrlRef>(null);
  43. const groups = useLegacyStore(GroupStore);
  44. const group = (groups as Group[]).find(item => item.id === groupId);
  45. const issueUrl =
  46. includeEventId && event
  47. ? window.location.origin +
  48. normalizeUrl(
  49. `/organizations/${organization.slug}/issues/${group?.id}/events/${event.id}/`
  50. )
  51. : window.location.origin +
  52. normalizeUrl(`/organizations/${organization.slug}/issues/${group?.id}/`);
  53. const markdownLink = `[${group?.shortId}](${issueUrl})`;
  54. const {onClick: handleCopyIssueLink} = useCopyToClipboard({
  55. text: issueUrl,
  56. successMessage: t('Copied Issue Link to clipboard'),
  57. onCopy: closeModal,
  58. });
  59. const {onClick: handleCopyMarkdownLink} = useCopyToClipboard({
  60. text: markdownLink,
  61. successMessage: t('Copied Markdown link to clipboard'),
  62. onCopy: closeModal,
  63. });
  64. return (
  65. <Fragment>
  66. <Header closeButton>
  67. <h4>{t('Share Issue')}</h4>
  68. </Header>
  69. <Body>
  70. <ModalContent>
  71. <UrlContainer>
  72. <TextContainer>
  73. <StyledAutoSelectText ref={urlRef}>{issueUrl}</StyledAutoSelectText>
  74. </TextContainer>
  75. </UrlContainer>
  76. {event && (
  77. <CheckboxContainer>
  78. <Checkbox
  79. checked={includeEventId}
  80. onChange={() => setIncludeEventId(!includeEventId)}
  81. />
  82. {t('Include Event ID in link')}
  83. </CheckboxContainer>
  84. )}
  85. <StyledButtonBar gap={0.5}>
  86. <Button
  87. size="sm"
  88. onClick={handleCopyMarkdownLink}
  89. analyticsEventKey="issue_details.copy_issue_markdown_link_clicked"
  90. analyticsEventName="Issue Details: Copy Issue Markdown Link"
  91. analyticsParams={{
  92. ...getAnalyticsDataForGroup(group),
  93. streamline: true,
  94. }}
  95. >
  96. {t('Copy as Markdown')}
  97. </Button>
  98. <Button
  99. priority="primary"
  100. size="sm"
  101. onClick={handleCopyIssueLink}
  102. analyticsEventKey={
  103. includeEventId
  104. ? 'issue_details.copy_event_link_clicked'
  105. : 'issue_details.copy_issue_url_clicked'
  106. }
  107. analyticsEventName={
  108. includeEventId
  109. ? 'Issue Details: Copy Event Link Clicked'
  110. : 'Issue Details: Copy Issue URL'
  111. }
  112. analyticsParams={
  113. includeEventId && event
  114. ? {
  115. ...getAnalyticsDataForGroup(group),
  116. ...getAnalyticsDataForEvent(event),
  117. streamline: true,
  118. }
  119. : {
  120. ...getAnalyticsDataForGroup(group),
  121. streamline: true,
  122. }
  123. }
  124. >
  125. {t('Copy Link')}
  126. </Button>
  127. </StyledButtonBar>
  128. </ModalContent>
  129. </Body>
  130. </Fragment>
  131. );
  132. }
  133. const ModalContent = styled('div')`
  134. display: flex;
  135. gap: ${space(1)};
  136. flex-direction: column;
  137. `;
  138. const UrlContainer = styled('div')`
  139. display: grid;
  140. grid-template-columns: 1fr max-content max-content;
  141. align-items: center;
  142. border: 1px solid ${p => p.theme.border};
  143. border-radius: ${space(0.5)};
  144. width: 100%;
  145. `;
  146. const StyledAutoSelectText = styled(AutoSelectText)`
  147. padding: ${space(1)} ${space(1)};
  148. ${p => p.theme.overflowEllipsis}
  149. `;
  150. const TextContainer = styled('div')`
  151. position: relative;
  152. display: flex;
  153. flex-grow: 1;
  154. background-color: transparent;
  155. border-right: 1px solid ${p => p.theme.border};
  156. min-width: 0;
  157. `;
  158. const CheckboxContainer = styled('div')`
  159. display: flex;
  160. gap: ${space(1)};
  161. align-items: center;
  162. `;
  163. const StyledButtonBar = styled(ButtonBar)`
  164. justify-content: flex-end;
  165. `;