Skip to content
Merged
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
172 changes: 172 additions & 0 deletions static/app/views/issueDetails/streamline/sidebar/seerDrawer.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ describe('SeerDrawer', () => {

beforeEach(() => {
MockApiClient.clearMockResponses();
localStorage.clear();

MockApiClient.addMockResponse({
url: `/organizations/${mockProject.organization.slug}/issues/${mockGroup.id}/autofix/setup/`,
Expand Down Expand Up @@ -136,12 +137,26 @@ describe('SeerDrawer', () => {
url: `/organizations/${mockProject.organization.slug}/group-search-views/starred/`,
body: [],
});
MockApiClient.addMockResponse({
url: `/organizations/${mockProject.organization.slug}/group-search-views/`,
body: [],
});
MockApiClient.addMockResponse({
url: `/projects/${mockProject.organization.slug}/${mockProject.slug}/`,
body: {
autofixAutomationTuning: 'off',
},
});
MockApiClient.addMockResponse({
url: `/organizations/${mockProject.organization.slug}/integrations/coding-agents/`,
body: {
integrations: [],
},
});
MockApiClient.addMockResponse({
url: `/projects/${mockProject.organization.slug}/${mockProject.slug}/autofix-repos/`,
body: [],
});
});

it('renders consent state if not consented', async () => {
Expand Down Expand Up @@ -633,4 +648,161 @@ describe('SeerDrawer', () => {
screen.getByText(/It currently only supports GitHub repositories/)
).toBeInTheDocument();
});

it('shows cursor integration onboarding step if integration is installed but handoff not configured', async () => {
MockApiClient.addMockResponse({
url: `/organizations/${mockProject.organization.slug}/integrations/coding-agents/`,
body: {
integrations: [
{
id: '123',
provider: 'cursor',
name: 'Cursor',
},
],
},
});
MockApiClient.addMockResponse({
url: `/projects/${mockProject.organization.slug}/${mockProject.slug}/seer/preferences/`,
body: {
code_mapping_repos: [],
preference: {
repositories: [{external_id: 'repo-123', name: 'org/repo', provider: 'github'}],
automated_run_stopping_point: 'root_cause',
// No automation_handoff
},
},
});
MockApiClient.addMockResponse({
url: `/projects/${mockProject.organization.slug}/${mockProject.slug}/`,
body: {
autofixAutomationTuning: 'medium',
seerScannerAutomation: true,
},
});
MockApiClient.addMockResponse({
url: `/organizations/${mockProject.organization.slug}/issues/${mockGroup.id}/autofix/`,
body: {autofix: null},
});
MockApiClient.addMockResponse({
url: `/projects/${mockProject.organization.slug}/${mockProject.slug}/autofix-repos/`,
body: [
{
name: 'org/repo',
provider: 'github',
owner: 'org',
external_id: 'repo-123',
is_readable: true,
is_writeable: true,
},
],
});
MockApiClient.addMockResponse({
url: `/organizations/${mockProject.organization.slug}/group-search-views/starred/`,
body: [
{
id: '1',
name: 'Fixability View',
query: 'is:unresolved issue.seer_actionability:high',
starred: true,
},
],
});

render(<SeerDrawer event={mockEvent} group={mockGroup} project={mockProject} />, {
organization: OrganizationFixture({
features: ['gen-ai-features', 'integrations-cursor', 'issue-views'],
}),
});

await waitForElementToBeRemoved(() =>
screen.queryByTestId('ai-setup-loading-indicator')
);

expect(
await screen.findByText('Hand Off to Cursor Background Agents')
).toBeInTheDocument();
expect(
screen.getByRole('button', {name: 'Set Seer to hand off to Cursor'})
).toBeInTheDocument();
});

it('does not show cursor integration step if localStorage skip key is set', async () => {
// Set skip key BEFORE rendering
localStorage.setItem(`seer-onboarding-cursor-skipped:${mockProject.id}`, 'true');

MockApiClient.addMockResponse({
url: `/organizations/${mockProject.organization.slug}/integrations/coding-agents/`,
body: {
integrations: [
{
id: '123',
provider: 'cursor',
name: 'Cursor',
},
],
},
});
MockApiClient.addMockResponse({
url: `/projects/${mockProject.organization.slug}/${mockProject.slug}/seer/preferences/`,
body: {
code_mapping_repos: [],
preference: {
repositories: [{external_id: 'repo-123', name: 'org/repo', provider: 'github'}],
automated_run_stopping_point: 'root_cause',
// No automation_handoff
},
},
});
MockApiClient.addMockResponse({
url: `/projects/${mockProject.organization.slug}/${mockProject.slug}/`,
body: {
autofixAutomationTuning: 'medium',
seerScannerAutomation: true,
},
});
MockApiClient.addMockResponse({
url: `/organizations/${mockProject.organization.slug}/issues/${mockGroup.id}/autofix/`,
body: {autofix: null},
});
MockApiClient.addMockResponse({
url: `/projects/${mockProject.organization.slug}/${mockProject.slug}/autofix-repos/`,
body: [
{
name: 'org/repo',
provider: 'github',
owner: 'org',
external_id: 'repo-123',
is_readable: true,
is_writeable: true,
},
],
});
MockApiClient.addMockResponse({
url: `/organizations/${mockProject.organization.slug}/group-search-views/starred/`,
body: [
{
id: '1',
name: 'Fixability View',
query: 'is:unresolved issue.seer_actionability:high',
starred: true,
},
],
});

render(<SeerDrawer event={mockEvent} group={mockGroup} project={mockProject} />, {
organization: OrganizationFixture({
features: ['gen-ai-features', 'integrations-cursor', 'issue-views'],
}),
});

await waitForElementToBeRemoved(() =>
screen.queryByTestId('ai-setup-loading-indicator')
);

// Should not show the step since it was skipped
expect(
screen.queryByText('Hand Off to Cursor Background Agents')
).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ describe('SeerNotices', () => {
url: `/projects/${organization.slug}/${ProjectFixture().slug}/autofix-repos/`,
body: [createRepository()],
});
MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/integrations/coding-agents/`,
body: {
integrations: [],
},
});
});

it('shows automation step if automation is allowed and tuning is off', async () => {
Expand Down Expand Up @@ -98,6 +104,167 @@ describe('SeerNotices', () => {
});
});

it('shows cursor integration step if integration is installed but handoff not configured', async () => {
MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/integrations/coding-agents/`,
body: {
integrations: [
{
id: '123',
provider: 'cursor',
name: 'Cursor',
},
],
},
});
MockApiClient.addMockResponse({
url: `/projects/${organization.slug}/${ProjectFixture().slug}/seer/preferences/`,
body: {
code_mapping_repos: [],
preference: {
repositories: [],
automated_run_stopping_point: 'root_cause',
// No automation_handoff - handoff is not configured
},
},
});
MockApiClient.addMockResponse({
method: 'GET',
url: `/projects/${organization.slug}/${ProjectFixture().slug}/`,
body: {
autofixAutomationTuning: 'medium',
},
});
MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/group-search-views/starred/`,
body: [
GroupSearchViewFixture({
query: 'is:unresolved issue.seer_actionability:high',
starred: true,
}),
],
});
const project = getProjectWithAutomation('medium');
render(<SeerNotices groupId="123" hasGithubIntegration project={project} />, {
organization: {
...organization,
features: ['integrations-cursor'],
},
});
await waitFor(() => {
expect(
screen.getByText('Hand Off to Cursor Background Agents')
).toBeInTheDocument();
});
});

it('does not show cursor integration step if localStorage skip key is set', () => {
// Set localStorage skip key
localStorage.setItem(`seer-onboarding-cursor-skipped:${ProjectFixture().id}`, 'true');

MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/integrations/coding-agents/`,
body: {
integrations: [
{
id: '123',
provider: 'cursor',
name: 'Cursor',
},
],
},
});
MockApiClient.addMockResponse({
method: 'GET',
url: `/projects/${organization.slug}/${ProjectFixture().slug}/`,
body: {
autofixAutomationTuning: 'medium',
},
});
MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/group-search-views/starred/`,
body: [
GroupSearchViewFixture({
query: 'is:unresolved issue.seer_actionability:high',
starred: true,
}),
],
});
const project = getProjectWithAutomation('medium');
render(<SeerNotices groupId="123" hasGithubIntegration project={project} />, {
organization: {
...organization,
features: ['integrations-cursor'],
},
});

// Should not show the cursor step since it was skipped
expect(
screen.queryByText('Hand Off to Cursor Background Agents')
).not.toBeInTheDocument();

// Clean up localStorage
localStorage.removeItem(`seer-onboarding-cursor-skipped:${ProjectFixture().id}`);
});

it('does not show cursor integration step if handoff is already configured', () => {
MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/integrations/coding-agents/`,
body: {
integrations: [
{
id: '123',
provider: 'cursor',
name: 'Cursor',
},
],
},
});
MockApiClient.addMockResponse({
url: `/projects/${organization.slug}/${ProjectFixture().slug}/seer/preferences/`,
body: {
code_mapping_repos: [],
preference: {
repositories: [],
automated_run_stopping_point: 'root_cause',
automation_handoff: {
handoff_point: 'root_cause',
target: 'cursor_background_agent',
integration_id: 123,
},
},
},
});
MockApiClient.addMockResponse({
method: 'GET',
url: `/projects/${organization.slug}/${ProjectFixture().slug}/`,
body: {
autofixAutomationTuning: 'medium',
},
});
MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/group-search-views/starred/`,
body: [
GroupSearchViewFixture({
query: 'is:unresolved issue.seer_actionability:high',
starred: true,
}),
],
});
const project = getProjectWithAutomation('medium');
render(<SeerNotices groupId="123" hasGithubIntegration project={project} />, {
organization: {
...organization,
features: ['integrations-cursor'],
},
});

// Should not show the cursor step since handoff is already configured
expect(
screen.queryByText('Hand Off to Cursor Background Agents')
).not.toBeInTheDocument();
});

it('does not render guided steps if all onboarding steps are complete', () => {
MockApiClient.addMockResponse({
method: 'GET',
Expand Down Expand Up @@ -126,5 +293,8 @@ describe('SeerNotices', () => {
expect(screen.queryByText('Pick Repositories to Work In')).not.toBeInTheDocument();
expect(screen.queryByText('Unleash Automation')).not.toBeInTheDocument();
expect(screen.queryByText('Get Some Quick Wins')).not.toBeInTheDocument();
expect(
screen.queryByText('Hand Off to Cursor Background Agents')
).not.toBeInTheDocument();
});
});
Loading
Loading