Skip to content

Commit

Permalink
split up + tests
Browse files Browse the repository at this point in the history
  • Loading branch information
roggenkemper committed Feb 13, 2025
1 parent dd515a6 commit 2da250e
Show file tree
Hide file tree
Showing 9 changed files with 347 additions and 543 deletions.
4 changes: 2 additions & 2 deletions static/app/views/issueDetails/actions/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,10 @@ describe('GroupActions', function () {
);

await userEvent.click(screen.getByLabelText('More Actions'));
await userEvent.click(await screen.findByText('Share'));
await userEvent.click(await screen.findByText('Publish'));

const modal = screen.getByRole('dialog');
expect(within(modal).getByText('Share Issue')).toBeInTheDocument();
expect(within(modal).getByText('Publish Issue')).toBeInTheDocument();
});

describe('delete', function () {
Expand Down
45 changes: 30 additions & 15 deletions static/app/views/issueDetails/actions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ import {useNavigate} from 'sentry/utils/useNavigate';
import useOrganization from 'sentry/utils/useOrganization';
import {hasDatasetSelector} from 'sentry/views/dashboards/utils';
import {NewIssueExperienceButton} from 'sentry/views/issueDetails/actions/newIssueExperienceButton';
import PublishIssueModal from 'sentry/views/issueDetails/actions/publishModal';
import ShareIssueModal from 'sentry/views/issueDetails/actions/shareModal';
import SubscribeAction from 'sentry/views/issueDetails/actions/subscribeAction';
import {Divider} from 'sentry/views/issueDetails/divider';
import {useHasStreamlinedUI} from 'sentry/views/issueDetails/utils';

import StreamlinedShareIssueModal from './streamlinedShareModal';
import SubscribeAction from './subscribeAction';

type UpdateData =
| {isBookmarked: boolean}
| {isSubscribed: boolean}
Expand Down Expand Up @@ -338,14 +338,24 @@ export function GroupActions({group, project, disabled, event}: GroupActionsProp
openModal(renderDiscardModal);
};

const openStreamlinedShareModal = () => {
const openShareModal = () => {
openModal(modalProps => (
<ShareIssueModal
{...modalProps}
organization={organization}
groupId={group.id}
event={event}
/>
));
};

const openPublishModal = () => {
openModal(modalProps => (
<StreamlinedShareIssueModal
<PublishIssueModal
{...modalProps}
organization={organization}
projectSlug={group.project.slug}
groupId={group.id}
eventId={event?.id}
onToggle={onToggleShare}
/>
));
Expand Down Expand Up @@ -448,14 +458,13 @@ export function GroupActions({group, project, disabled, event}: GroupActionsProp
icon={group.isSubscribed ? <IconSubscribed /> : <IconUnsubscribed />}
size="sm"
/>
{shareCap.enabled && (
<Button
size="sm"
onClick={openStreamlinedShareModal}
icon={<IconLink />}
aria-label={t('Share')}
/>
)}
<Button
size="sm"
onClick={openShareModal}
icon={<IconLink />}
aria-label={t('Share')}
title={t('Share Issue')}
/>
</Fragment>
))}
<DropdownMenu
Expand Down Expand Up @@ -508,12 +517,18 @@ export function GroupActions({group, project, disabled, event}: GroupActionsProp
details: !group.inbox || disabled ? t('Issue has been reviewed') : undefined,
onAction: () => onUpdate({inbox: false}),
},

{
key: bookmarkKey,
label: bookmarkTitle,
onAction: onToggleBookmark,
},
{
key: 'publish',
label: t('Publish'),
disabled: disabled || !shareCap.enabled,
hidden: !organization.features.includes('shared-issues'),
onAction: openPublishModal,
},
{
key: 'reprocess',
label: t('Reprocess events'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {act, renderGlobalModal, screen, userEvent} from 'sentry-test/reactTestin
import {openModal} from 'sentry/actionCreators/modal';
import GroupStore from 'sentry/stores/groupStore';
import ModalStore from 'sentry/stores/modalStore';
import ShareIssueModal from 'sentry/views/issueDetails/actions/shareModal';
import PublishIssueModal from 'sentry/views/issueDetails/actions/publishModal';

describe('shareModal', () => {
const project = ProjectFixture();
Expand Down Expand Up @@ -37,7 +37,7 @@ describe('shareModal', () => {

act(() =>
openModal(modalProps => (
<ShareIssueModal
<PublishIssueModal
{...modalProps}
groupId={group.id}
organization={organization}
Expand All @@ -47,8 +47,8 @@ describe('shareModal', () => {
))
);

expect(screen.getByText('Share Issue')).toBeInTheDocument();
await userEvent.click(screen.getByLabelText('Share'));
expect(screen.getByText('Publish Issue')).toBeInTheDocument();
await userEvent.click(screen.getByLabelText('Publish'));
expect(await screen.findByRole('button', {name: 'Copy Link'})).toBeInTheDocument();
expect(issuesApi).toHaveBeenCalledTimes(1);
expect(onToggle).toHaveBeenCalledTimes(1);
Expand All @@ -67,7 +67,7 @@ describe('shareModal', () => {

act(() =>
openModal(modalProps => (
<ShareIssueModal
<PublishIssueModal
{...modalProps}
groupId={group.id}
organization={organization}
Expand All @@ -77,9 +77,8 @@ describe('shareModal', () => {
))
);

await userEvent.click(screen.getByLabelText('Unshare'));
await userEvent.click(screen.getByLabelText('Unpublish'));

expect(await screen.findByRole('button', {name: 'Close'})).toBeInTheDocument();
expect(issuesApi).toHaveBeenCalledTimes(1);
expect(onToggle).toHaveBeenCalledTimes(1);
});
Expand Down
207 changes: 207 additions & 0 deletions static/app/views/issueDetails/actions/publishModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import {Fragment, useCallback, useRef, useState} from 'react';
import styled from '@emotion/styled';

import {bulkUpdate} from 'sentry/actionCreators/group';
import {addErrorMessage} from 'sentry/actionCreators/indicator';
import type {ModalRenderProps} from 'sentry/actionCreators/modal';
import AutoSelectText from 'sentry/components/autoSelectText';
import {Button} from 'sentry/components/button';
import LoadingIndicator from 'sentry/components/loadingIndicator';
import Switch from 'sentry/components/switchButton';
import {IconRefresh} from 'sentry/icons';
import {t} from 'sentry/locale';
import GroupStore from 'sentry/stores/groupStore';
import {useLegacyStore} from 'sentry/stores/useLegacyStore';
import {space} from 'sentry/styles/space';
import type {Group} from 'sentry/types/group';
import type {Organization} from 'sentry/types/organization';
import useApi from 'sentry/utils/useApi';
import useCopyToClipboard from 'sentry/utils/useCopyToClipboard';

interface ShareIssueModalProps extends ModalRenderProps {
groupId: string;
onToggle: () => void;
organization: Organization;
projectSlug: string;
}

type UrlRef = React.ElementRef<typeof AutoSelectText>;

export function getShareUrl(group: Group) {
const path = `/share/issue/${group.shareId}/`;
const {host, protocol} = window.location;
return `${protocol}//${host}${path}`;
}

export default function PublishIssueModal({
Header,
Body,
organization,
projectSlug,
groupId,
onToggle,
closeModal,
}: ShareIssueModalProps) {
const api = useApi({persistInFlight: true});
const [loading, setLoading] = useState(false);
const urlRef = useRef<UrlRef>(null);
const groups = useLegacyStore(GroupStore);
const group = (groups as Group[]).find(item => item.id === groupId);
const isPublished = group?.isPublic;

const handleShare = useCallback(
(e: React.MouseEvent<HTMLButtonElement> | null, reshare?: boolean) => {
e?.preventDefault();
setLoading(true);
onToggle();

bulkUpdate(
api,
{
orgId: organization.slug,
projectId: projectSlug,
itemIds: [groupId],
data: {
isPublic: reshare ?? !isPublished,
},
},
{
error: () => {
addErrorMessage(t('Error sharing'));
},
complete: () => {
setLoading(false);
},
}
);
},
[api, setLoading, onToggle, isPublished, organization.slug, projectSlug, groupId]
);

const shareUrl = group?.shareId ? getShareUrl(group) : null;

const {onClick: handleCopy} = useCopyToClipboard({
text: shareUrl!,
onCopy: closeModal,
});

return (
<Fragment>
<Header closeButton>
<h4>{t('Publish Issue')}</h4>
</Header>
<Body>
<ModalContent>
<SwitchWrapper>
<div>
<Title>{t('Create a public link')}</Title>
<SubText>{t('Share a link with anyone outside your organization')}</SubText>
</div>
<Switch
aria-label={isPublished ? t('Unpublish') : t('Publish')}
isActive={isPublished}
size="lg"
toggle={handleShare}
/>
</SwitchWrapper>
{(!group || loading) && (
<LoadingContainer>
<LoadingIndicator mini />
</LoadingContainer>
)}
{group && !loading && isPublished && shareUrl && (
<PublishActions>
<UrlContainer>
<TextContainer>
<StyledAutoSelectText ref={urlRef}>{shareUrl}</StyledAutoSelectText>
</TextContainer>
<ReshareButton
title={t('Generate new URL. Invalidates previous URL')}
aria-label={t('Generate new URL')}
borderless
size="sm"
icon={<IconRefresh />}
onClick={() => handleShare(null, true)}
/>
</UrlContainer>
<ButtonContainer>
<Button priority="primary" onClick={handleCopy}>
{t('Copy Link')}
</Button>
</ButtonContainer>
</PublishActions>
)}
</ModalContent>
</Body>
</Fragment>
);
}

/**
* min-height reduces layout shift when switching on and off
*/
const ModalContent = styled('div')`
display: flex;
gap: ${space(2)};
flex-direction: column;
min-height: 100px;
`;

const SwitchWrapper = styled('div')`
display: flex;
justify-content: space-between;
align-items: center;
gap: ${space(2)};
`;

const Title = styled('div')`
padding-right: ${space(4)};
white-space: nowrap;
`;

const SubText = styled('p')`
color: ${p => p.theme.subText};
font-size: ${p => p.theme.fontSizeSmall};
`;

const LoadingContainer = styled('div')`
display: flex;
justify-content: center;
`;

const UrlContainer = styled('div')`
display: grid;
grid-template-columns: 1fr max-content max-content;
align-items: center;
border: 1px solid ${p => p.theme.border};
border-radius: ${space(0.5)};
`;

const StyledAutoSelectText = styled(AutoSelectText)`
padding: ${space(1)} ${space(1)};
${p => p.theme.overflowEllipsis}
`;

const TextContainer = styled('div')`
position: relative;
display: flex;
flex-grow: 1;
background-color: transparent;
border-right: 1px solid ${p => p.theme.border};
min-width: 0;
`;

const ReshareButton = styled(Button)`
border-radius: 0;
height: 100%;
flex-shrink: 0;
`;

const PublishActions = styled('div')`
display: flex;
gap: ${space(1)};
`;

const ButtonContainer = styled('div')`
align-self: flex-end;
`;
Loading

0 comments on commit 2da250e

Please sign in to comment.