Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 92 additions & 6 deletions static/app/components/actions/resolve.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@ describe('ResolveActions', () => {
describe('disabled', () => {
it('does not call onUpdate when clicked', async () => {
render(
<ResolveActions onUpdate={spy} disabled hasRelease={false} projectSlug="proj-1" />
<ResolveActions
hasSemverReleaseFeature={false}
onUpdate={spy}
disabled
hasRelease={false}
projectSlug="proj-1"
/>
);
const button = screen.getByRole('button', {name: 'Resolve'});
expect(button).toBeDisabled();
Expand All @@ -34,6 +40,7 @@ describe('ResolveActions', () => {
it('main button calls onUpdate when clicked and dropdown menu disabled', async () => {
render(
<ResolveActions
hasSemverReleaseFeature={false}
onUpdate={spy}
disableDropdown
hasRelease={false}
Expand All @@ -55,6 +62,7 @@ describe('ResolveActions', () => {
it('calls onUpdate with unresolved status when clicked', async () => {
render(
<ResolveActions
hasSemverReleaseFeature={false}
onUpdate={spy}
disabled
hasRelease={false}
Expand All @@ -80,6 +88,7 @@ describe('ResolveActions', () => {
it('cannot be unresolved manually', async () => {
render(
<ResolveActions
hasSemverReleaseFeature={false}
onUpdate={spy}
disabled
hasRelease={false}
Expand All @@ -96,7 +105,14 @@ describe('ResolveActions', () => {

describe('without confirmation', () => {
it('calls spy with resolved status when clicked', async () => {
render(<ResolveActions onUpdate={spy} hasRelease={false} projectSlug="proj-1" />);
render(
<ResolveActions
hasSemverReleaseFeature={false}
onUpdate={spy}
hasRelease={false}
projectSlug="proj-1"
/>
);
await userEvent.click(screen.getByRole('button', {name: 'Resolve'}));
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({
Expand All @@ -111,6 +127,7 @@ describe('ResolveActions', () => {
it('displays confirmation modal with message provided', async () => {
render(
<ResolveActions
hasSemverReleaseFeature={false}
onUpdate={spy}
hasRelease={false}
projectSlug="proj-1"
Expand Down Expand Up @@ -139,7 +156,14 @@ describe('ResolveActions', () => {
url: '/projects/org-slug/project-slug/releases/',
body: [ReleaseFixture()],
});
render(<ResolveActions hasRelease projectSlug="project-slug" onUpdate={onUpdate} />);
render(
<ResolveActions
hasSemverReleaseFeature={false}
hasRelease
projectSlug="project-slug"
onUpdate={onUpdate}
/>
);
renderGlobalModal();

await userEvent.click(screen.getByLabelText('More resolve options'));
Expand All @@ -160,9 +184,10 @@ describe('ResolveActions', () => {
});
});

it('displays if the current release version uses semver', async () => {
it('displays if the current release version uses semver and flag is not enabled', async () => {
render(
<ResolveActions
hasSemverReleaseFeature={false}
onUpdate={spy}
hasRelease
projectSlug="proj-1"
Expand All @@ -176,8 +201,59 @@ describe('ResolveActions', () => {
expect(screen.getByText('(semver)')).toBeInTheDocument();
});

it('shows resolve in semver release option when the current release version uses semver and flag is enabled', async () => {
MockApiClient.addMockResponse({
url: '/organizations/org-slug/releases/',
body: [ReleaseFixture()],
});

render(
<ResolveActions
hasSemverReleaseFeature
onUpdate={spy}
hasRelease
projectSlug="proj-1"
/>
);

await userEvent.click(screen.getByLabelText('More resolve options'));
expect(screen.getByText('The current semver release')).toBeInTheDocument();
expect(screen.getByText('1.2.0')).toBeInTheDocument();
expect(screen.queryByText('The current release')).not.toBeInTheDocument();
});

it('shows resolve in latest release option when the current release version does not use semver and flag is enabled', async () => {
MockApiClient.addMockResponse({
url: '/organizations/org-slug/releases/',
body: [],
});

render(
<ResolveActions
hasSemverReleaseFeature
onUpdate={spy}
hasRelease
projectSlug="proj-1"
latestRelease={{version: 'frontend@abc123def'}}
/>
);

await userEvent.click(screen.getByLabelText('More resolve options'));
expect(screen.getByText('The current release')).toBeInTheDocument();
expect(screen.getByText('abc123def')).toBeInTheDocument();
expect(screen.getByText('(non-semver)')).toBeInTheDocument();
expect(screen.queryByText('The current semver release')).not.toBeInTheDocument();
});

it('displays prompt to setup releases when there are no releases', async () => {
render(<ResolveActions onUpdate={spy} hasRelease={false} projectSlug="proj-1" />);
render(
<ResolveActions
hasSemverReleaseFeature={false}
onUpdate={spy}
hasRelease={false}
projectSlug="proj-1"
/>
);

await userEvent.click(screen.getByLabelText('More resolve options'));
expect(screen.getByText('Resolving is better with Releases')).toBeInTheDocument();
Expand All @@ -186,6 +262,7 @@ describe('ResolveActions', () => {
it('does not prompt to setup releases when multiple projects are selected', async () => {
render(
<ResolveActions
hasSemverReleaseFeature={false}
onUpdate={spy}
hasRelease={false}
projectSlug="proj-1"
Expand All @@ -205,6 +282,7 @@ describe('ResolveActions', () => {
it('does render more resolve options', () => {
render(
<ResolveActions
hasSemverReleaseFeature={false}
onUpdate={spy}
hasRelease={false}
projectSlug="proj-1"
Expand All @@ -217,6 +295,7 @@ describe('ResolveActions', () => {
it('does not render more resolve options', () => {
render(
<ResolveActions
hasSemverReleaseFeature={false}
onUpdate={spy}
hasRelease={false}
projectSlug="proj-1"
Expand All @@ -232,7 +311,14 @@ describe('ResolveActions', () => {
url: '/projects/org-slug/project-slug/releases/',
body: [ReleaseFixture()],
});
render(<ResolveActions hasRelease projectSlug="project-slug" onUpdate={onUpdate} />);
render(
<ResolveActions
hasSemverReleaseFeature={false}
hasRelease
projectSlug="project-slug"
onUpdate={onUpdate}
/>
);

await userEvent.click(screen.getByLabelText('More resolve options'));
expect(await screen.findByText('The next release')).toBeInTheDocument();
Expand Down
11 changes: 2 additions & 9 deletions static/app/components/actions/resolve.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ function SetupReleasesPrompt() {

interface ResolveActionsProps {
hasRelease: boolean;
hasSemverReleaseFeature: boolean;
onUpdate: (data: GroupStatusResolution) => void;
confirmLabel?: string;
confirmMessage?: React.ReactNode;
disableDropdown?: boolean;
disableResolveInRelease?: boolean;
disabled?: boolean;
hasSemverReleaseFeature?: boolean; // TODO(michellewzhang): this should be required eventually
isAutoResolved?: boolean;
isResolved?: boolean;
latestRelease?: Project['latestRelease'];
Expand Down Expand Up @@ -302,14 +302,7 @@ function ResolveActions({
)}
disabledKeys={
multipleProjectsSelected
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we won't run the latestSemverRelease hook call if multiple projects are selected, so no point in calling here

? [
'next-release',
...(hasSemverReleaseFeature && latestSemverRelease?.version
? ['semver-release']
: ['current-release']),
'another-release',
'a-commit',
]
? ['next-release', 'current-release', 'another-release', 'a-commit']
: disabled || !hasRelease
? [
'next-release',
Expand Down
1 change: 1 addition & 0 deletions static/app/views/issueDetails/actions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,7 @@ export function GroupActions({group, project, disabled, event}: GroupActionsProp
<GuideAnchor target="resolve" position="bottom" offset={20}>
<ResolveActions
disableResolveInRelease={!resolveInReleaseCap.enabled}
hasSemverReleaseFeature={hasSemverReleaseFeature}
disabled={disabled}
disableDropdown={disabled}
hasRelease={hasRelease}
Expand Down
12 changes: 12 additions & 0 deletions static/app/views/issueList/actions/resolveActions.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import ResolveActions from 'sentry/components/actions/resolve';
import useOrganization from 'sentry/utils/useOrganization';
import useProjects from 'sentry/utils/useProjects';
import useProjectReleaseVersionIsSemver from 'sentry/views/issueDetails/useProjectReleaseVersionIsSemver';

import type {getConfirm, getLabel} from './utils';
import {ConfirmAction} from './utils';
Expand All @@ -21,6 +23,7 @@ function ResolveActionsContainer({
confirm,
label,
}: Props) {
const organization = useOrganization();
const {initiallyLoaded, projects, fetchError} = useProjects({
slugs: selectedProjectSlug ? [selectedProjectSlug] : [],
});
Expand All @@ -35,6 +38,14 @@ function ResolveActionsContainer({
const latestRelease =
project && 'latestRelease' in project ? project.latestRelease : undefined;

const projHasSemverRelease = useProjectReleaseVersionIsSemver({
version: project?.latestRelease?.version,
enabled: Boolean(project),
});

const hasSemverReleaseFeature =
organization.features?.includes('resolve-in-semver-release') && projHasSemverRelease;

// resolve requires a single project to be active in an org context
// projectId is null when 0 or >1 projects are selected.
const resolveDisabled = Boolean(!anySelected || fetchError);
Expand All @@ -55,6 +66,7 @@ function ResolveActionsContainer({
disabled={resolveDisabled}
disableDropdown={resolveDropdownDisabled}
projectFetchError={Boolean(fetchError)}
hasSemverReleaseFeature={hasSemverReleaseFeature}
/>
);
}
Expand Down
Loading