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
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

### Improvements

- **RovoDev**: Added user entitlement type (RDS/RDE paid or unpaid seat) to the feedback collector context, enabling better triage and analysis of feedback by seat type
- **RovoDev**: Upgraded Rovo Dev to v0.13.63
- **RovoDev**: Generalized MCP tool parsing in chat UI to support any MCP toolset via regex matching (`mcp__<name>__invoke_tool` / `mcp__<name>__get_tool_schema`) instead of hardcoded tool names


## What's new in 4.0.27

### Improvements
Expand Down Expand Up @@ -45,7 +45,6 @@

- **RovoDev**: Added copy code button within the Rovo Dev chat for code blocks in the chat.


## What's new in 4.0.23

### Features
Expand Down
89 changes: 89 additions & 0 deletions src/rovo-dev/rovoDevFeedbackManager.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
jest.mock('src/logger');
jest.mock('vscode');

const mockCheckEntitlement = jest.fn();

jest.mock('src/container', () => ({
Container: {
rovoDevEntitlementChecker: {
checkEntitlement: mockCheckEntitlement,
},
},
}));

const mockExtensionApiInstance = {
metadata: {
version: jest.fn(),
Expand Down Expand Up @@ -41,6 +51,7 @@ describe('RovoDevFeedbackManager', () => {

// Default return values
mockExtensionApiInstance.metadata.version.mockReturnValue('1.0.0');
mockCheckEntitlement.mockResolvedValue({ isEntitled: true, type: 'ROVO_DEV_STANDARD' });
});

describe('submitFeedback', () => {
Expand Down Expand Up @@ -246,5 +257,83 @@ describe('RovoDevFeedbackManager', () => {
}),
);
});

it('should include entitlement type in context', async () => {
mockCheckEntitlement.mockResolvedValue({ isEntitled: true, type: 'ROVO_DEV_EVERYWHERE' });

const feedback = {
feedbackType: 'general' as const,
feedbackMessage: 'Test feedback',
canContact: false,
};

await RovoDevFeedbackManager.submitFeedback(feedback);

expect(mockTransport).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
data: expect.objectContaining({
fields: expect.arrayContaining([
expect.objectContaining({
id: 'customfield_10047',
value: expect.stringContaining('"entitlementType": "ROVO_DEV_EVERYWHERE"'),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔎 Testing

The test assertion uses expect.stringContaining to check for JSON content, but this approach is fragile and could match partial strings incorrectly.

Details

📖 Explanation: Using JSON.parse on the value and then checking the parsed object's properties would be more robust and less prone to false positives than string matching.

Uses AI. Verify results. Give Feedback

}),
]),
}),
}),
);
});

it('should include entitlement type for non-entitled users', async () => {
mockCheckEntitlement.mockResolvedValue({ isEntitled: false, type: 'NO_ACTIVE_PRODUCT' });

const feedback = {
feedbackType: 'bug' as const,
feedbackMessage: 'Bug with entitlement',
canContact: false,
};

await RovoDevFeedbackManager.submitFeedback(feedback);

expect(mockTransport).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
data: expect.objectContaining({
fields: expect.arrayContaining([
expect.objectContaining({
id: 'customfield_10047',
value: expect.stringContaining('"entitlementType": "NO_ACTIVE_PRODUCT"'),
}),
]),
}),
}),
);
});

it('should default entitlement type to unknown when entitlement check fails', async () => {
mockCheckEntitlement.mockRejectedValue(new Error('Entitlement check failed'));

const feedback = {
feedbackType: 'general' as const,
feedbackMessage: 'Test feedback',
canContact: false,
};

await RovoDevFeedbackManager.submitFeedback(feedback);

expect(mockTransport).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
data: expect.objectContaining({
fields: expect.arrayContaining([
expect.objectContaining({
id: 'customfield_10047',
value: expect.stringContaining('"entitlementType": "unknown"'),
}),
]),
}),
}),
);
});
});
});
15 changes: 13 additions & 2 deletions src/rovo-dev/rovoDevFeedbackManager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { truncate } from 'lodash';
import { Container } from 'src/container';
import { UserInfo } from 'src/rovo-dev/api/extensionApiTypes';
import * as vscode from 'vscode';

Expand All @@ -23,7 +24,7 @@ export class RovoDevFeedbackManager {
isBBY: boolean = false,
): Promise<void> {
const transport = getAxiosInstance();
const context = this.getContext(isBBY, feedback.rovoDevSessionId);
const context = await this.getContext(isBBY, feedback.rovoDevSessionId);

let userEmail = 'do-not-reply@atlassian.com';
let userName = 'unknown';
Expand Down Expand Up @@ -97,13 +98,23 @@ export class RovoDevFeedbackManager {
}
}

private static getContext(isBBY: boolean = false, rovoDevSessionId?: string): any {
private static async getContext(isBBY: boolean = false, rovoDevSessionId?: string): Promise<any> {
const extensionApi = new ExtensionApi();

let entitlementType: string = 'unknown';
try {
const entitlement = await Container.rovoDevEntitlementChecker.checkEntitlement();
entitlementType = entitlement.type;
} catch {
entitlementType = 'unknown';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔎 Code Readability

The variable assignment on line 109 is redundant since entitlementType is already initialized to 'unknown' on line 104.

Details

📖 Explanation: Removing the redundant assignment in the catch block would simplify the code since the variable is already initialized with the fallback value.

Suggested change
entitlementType = 'unknown';
// Intentionally leave entitlementType as 'unknown' when the entitlement check fails

Uses AI. Verify results. Give Feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔎 Code Readability

The error handling in the catch block reassigns entitlementType to 'unknown' which is redundant since it's already initialized to 'unknown'.

Details

📖 Explanation: Removing the redundant assignment in the catch block simplifies the code since the variable is already initialized with the fallback value, making the error handling more concise.

Suggested change
entitlementType = 'unknown';
// entitlementType remains 'unknown' if the entitlement check fails

Uses AI. Verify results. Give Feedback

}

return {
component: isBBY ? 'Boysenberry - vscode' : 'IDE - vscode',
extensionVersion: extensionApi.metadata.version(),
vscodeVersion: vscode.version,
rovoDevVersion: MIN_SUPPORTED_ROVODEV_VERSION,
entitlementType,
...(rovoDevSessionId && { rovoDevSessionId }),
};
}
Expand Down
Loading