Skip to content

Commit b05bd7d

Browse files
gblanc-1aclaude
andcommitted
feat(ui): add Report Issue, Request Feature, and Retry Feedback
Add Report Issue and Request Feature links in marketplace bundle cards and TreeView context menu. Opens source repository issues page with pre-filled bug or feature template. Add Retry Feedback Submission to context menu for re-submitting locally saved feedback that failed due to network issues. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 03fd6ca commit b05bd7d

File tree

5 files changed

+211
-2
lines changed

5 files changed

+211
-2
lines changed

package.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,24 @@
439439
"title": "Rate & Feedback",
440440
"category": "Prompt Registry",
441441
"icon": "$(star-full)"
442+
},
443+
{
444+
"command": "promptRegistry.reportIssue",
445+
"title": "Report Issue",
446+
"category": "Prompt Registry",
447+
"icon": "$(bug)"
448+
},
449+
{
450+
"command": "promptRegistry.requestFeature",
451+
"title": "Request Feature",
452+
"category": "Prompt Registry",
453+
"icon": "$(lightbulb)"
454+
},
455+
{
456+
"command": "promptRegistry.retryFeedback",
457+
"title": "Retry Feedback Submission",
458+
"category": "Prompt Registry",
459+
"icon": "$(sync)"
442460
}
443461
],
444462
"viewsContainers": {
@@ -659,6 +677,21 @@
659677
"when": "view == promptRegistryExplorer && viewItem =~ /^installed_bundle/",
660678
"group": "engagement@1"
661679
},
680+
{
681+
"command": "promptRegistry.reportIssue",
682+
"when": "view == promptRegistryExplorer && viewItem =~ /^installed_bundle/",
683+
"group": "engagement@2"
684+
},
685+
{
686+
"command": "promptRegistry.requestFeature",
687+
"when": "view == promptRegistryExplorer && viewItem =~ /^installed_bundle/",
688+
"group": "engagement@3"
689+
},
690+
{
691+
"command": "promptRegistry.retryFeedback",
692+
"when": "view == promptRegistryExplorer && viewItem =~ /^installed_bundle/",
693+
"group": "engagement@4"
694+
},
662695
{
663696
"command": "promptRegistry.moveToRepositoryCommit",
664697
"when": "view == promptRegistryExplorer && viewItem =~ /^installed_bundle.*_user$/",

src/commands/FeedbackCommands.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,18 @@ export class FeedbackCommands {
8585
vscode.commands.registerCommand(
8686
'promptRegistry.submitFeedback',
8787
(item: FeedbackableItem | any) => this.submitFeedback(this.normalizeFeedbackItem(item))
88+
),
89+
vscode.commands.registerCommand(
90+
'promptRegistry.reportIssue',
91+
(item: any) => this.reportIssue(this.normalizeFeedbackItem(item))
92+
),
93+
vscode.commands.registerCommand(
94+
'promptRegistry.requestFeature',
95+
(item: any) => this.requestFeature(this.normalizeFeedbackItem(item))
96+
),
97+
vscode.commands.registerCommand(
98+
'promptRegistry.retryFeedback',
99+
(item: any) => this.retryFeedback(this.normalizeFeedbackItem(item))
88100
)
89101
);
90102

@@ -354,6 +366,139 @@ export class FeedbackCommands {
354366
}
355367

356368

369+
/**
370+
* Report an issue for a bundle (opens issue tracker with bug template)
371+
*/
372+
async reportIssue(item: FeedbackableItem): Promise<void> {
373+
await this.openIssueTrackerWithTemplate(item, 'bug');
374+
}
375+
376+
/**
377+
* Request a feature for a bundle (opens issue tracker with feature template)
378+
*/
379+
async requestFeature(item: FeedbackableItem): Promise<void> {
380+
await this.openIssueTrackerWithTemplate(item, 'feature');
381+
}
382+
383+
/**
384+
* Retry submitting unsynced feedback for a bundle
385+
*/
386+
async retryFeedback(item: FeedbackableItem): Promise<void> {
387+
const storage = this.engagementService?.getStorage?.();
388+
if (!storage) return;
389+
390+
const unsynced = await storage.getUnsyncedFeedback();
391+
const pending = unsynced.filter(f => f.bundleId === item.resourceId);
392+
393+
if (pending.length === 0) {
394+
vscode.window.showInformationMessage('No pending feedback to retry.');
395+
return;
396+
}
397+
398+
for (const entry of pending) {
399+
try {
400+
if (this.engagementService) {
401+
await this.engagementService.submitFeedback(
402+
entry.resourceType,
403+
entry.bundleId,
404+
entry.comment || `Rated ${entry.rating} stars`,
405+
{ rating: entry.rating, hubId: entry.hubId || undefined }
406+
);
407+
await storage.markFeedbackSynced(entry.id);
408+
}
409+
} catch (error) {
410+
this.logger.warn(`Retry failed for ${entry.id}: ${error}`);
411+
}
412+
}
413+
414+
const stillUnsynced = (await storage.getUnsyncedFeedback()).filter(f => f.bundleId === item.resourceId);
415+
if (stillUnsynced.length === 0) {
416+
vscode.window.showInformationMessage('Feedback submitted successfully!');
417+
} else {
418+
vscode.window.showWarningMessage(`${stillUnsynced.length} feedback(s) still pending. Please try again later.`);
419+
}
420+
}
421+
422+
private async openIssueTrackerWithTemplate(
423+
item: FeedbackableItem,
424+
type: 'bug' | 'feature'
425+
): Promise<void> {
426+
try {
427+
if (!item.sourceUrl) {
428+
vscode.window.showWarningMessage('No source repository URL available for this bundle.');
429+
return;
430+
}
431+
432+
let issueUrl = item.sourceUrl;
433+
if (issueUrl.endsWith('.git')) {
434+
issueUrl = issueUrl.slice(0, -4);
435+
}
436+
437+
const githubMatch = issueUrl.match(/^(https?:\/\/github\.com\/[^/]+\/[^/]+)/);
438+
if (githubMatch) {
439+
issueUrl = `${githubMatch[1]}/issues/new`;
440+
} else if (!issueUrl.includes('/issues')) {
441+
issueUrl = `${issueUrl}/issues/new`;
442+
}
443+
444+
const isAwesomeCopilot = item.sourceType === 'awesome-copilot';
445+
const itemType = isAwesomeCopilot ? 'Collection' : 'Bundle';
446+
const name = item.name || item.resourceId;
447+
448+
let title: string;
449+
let bodyParts: string[];
450+
451+
if (type === 'bug') {
452+
title = `[Bug Report] ${name}`;
453+
bodyParts = [
454+
`${itemType} Information`,
455+
`- **${itemType} ID:** ${item.resourceId}`,
456+
...(item.version ? [`- **Version:** ${item.version}`] : []),
457+
'',
458+
'## Bug Description',
459+
'_Describe the bug clearly and concisely_',
460+
'',
461+
'## Steps to Reproduce',
462+
'1. ',
463+
'2. ',
464+
'3. ',
465+
'',
466+
'## Expected Behavior',
467+
'_What did you expect to happen?_',
468+
'',
469+
'## Actual Behavior',
470+
'_What actually happened?_',
471+
'',
472+
'## Additional Context',
473+
'_Any other information that might be helpful_',
474+
];
475+
} else {
476+
title = `[Feature Request] ${name}`;
477+
bodyParts = [
478+
`${itemType} Information`,
479+
`- **${itemType} ID:** ${item.resourceId}`,
480+
...(item.version ? [`- **Version:** ${item.version}`] : []),
481+
'',
482+
'## Feature Description',
483+
'_Describe the feature you would like_',
484+
'',
485+
'## Use Case',
486+
'_Why would this feature be useful?_',
487+
'',
488+
'## Additional Context',
489+
'_Any other information or examples_',
490+
];
491+
}
492+
493+
const params = new URLSearchParams({ title, body: bodyParts.join('\n') });
494+
const uri = vscode.Uri.parse(`${issueUrl}?${params.toString()}`, true);
495+
await vscode.env.openExternal(uri);
496+
} catch (error) {
497+
this.logger.warn('Could not open issue tracker', error as Error);
498+
vscode.window.showWarningMessage('Could not open issue tracker. Please visit the repository manually.');
499+
}
500+
}
501+
357502
/**
358503
* Save feedback to the engagement service
359504
*/

src/ui/MarketplaceViewProvider.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { PendingFeedback } from '../types/pendingFeedback';
2626
* Message types sent from webview to extension
2727
*/
2828
interface WebviewMessage {
29-
type: 'refresh' | 'install' | 'update' | 'uninstall' | 'openDetails' | 'openPromptFile' | 'installVersion' | 'getVersions' | 'toggleAutoUpdate' | 'openSourceRepository' | 'completeSetup' | 'getFeedbacks' | 'submitFeedback';
29+
type: 'refresh' | 'install' | 'update' | 'uninstall' | 'openDetails' | 'openPromptFile' | 'installVersion' | 'getVersions' | 'toggleAutoUpdate' | 'openSourceRepository' | 'completeSetup' | 'getFeedbacks' | 'submitFeedback' | 'reportIssue' | 'requestFeature';
3030
bundleId?: string;
3131
installPath?: string;
3232
filePath?: string;
@@ -553,6 +553,26 @@ export class MarketplaceViewProvider implements vscode.WebviewViewProvider {
553553
await this.handleWebviewFeedback(message.bundleId, message.rating, message.comment);
554554
}
555555
break;
556+
case 'reportIssue':
557+
case 'requestFeature':
558+
if (message.bundleId) {
559+
const command = message.type === 'reportIssue' ? 'promptRegistry.reportIssue' : 'promptRegistry.requestFeature';
560+
const bundles = await this.registryManager.searchBundles({ text: message.bundleId });
561+
const foundBundle = bundles.find(b => b.id === message.bundleId);
562+
if (foundBundle) {
563+
const allSources = await this.registryManager.listSources();
564+
const foundSource = allSources.find(s => s.id === foundBundle.sourceId);
565+
await vscode.commands.executeCommand(command, {
566+
resourceId: foundBundle.id,
567+
resourceType: 'bundle',
568+
name: foundBundle.name,
569+
version: foundBundle.version,
570+
sourceUrl: foundSource?.url,
571+
sourceType: foundSource?.type,
572+
});
573+
}
574+
}
575+
break;
556576
default:
557577
this.logger.warn(`Unknown message type: ${message.type}`);
558578
}

src/ui/webview/marketplace/marketplace.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,8 @@ function renderBundles() {
516516
<button class="btn btn-link" data-action="openSourceRepo" data-bundle-id="${bundle.id}" title="Open Source Repository">
517517
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M4.5 3A1.5 1.5 0 0 0 3 4.5v7A1.5 1.5 0 0 0 4.5 13h7a1.5 1.5 0 0 0 1.5-1.5v-2a.5.5 0 0 1 1 0v2a2.5 2.5 0 0 1-2.5 2.5h-7A2.5 2.5 0 0 1 2 11.5v-7A2.5 2.5 0 0 1 4.5 2h2a.5.5 0 0 1 0 1h-2zM9 2.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0V3.707l-5.146 5.147a.5.5 0 0 1-.708-.708L12.293 3H9.5a.5.5 0 0 1-.5-.5z"/></svg>
518518
</button>
519+
<button class="btn btn-link" data-action="reportIssue" data-bundle-id="${bundle.id}" title="Report Issue">Report Issue</button>
520+
<button class="btn btn-link" data-action="requestFeature" data-bundle-id="${bundle.id}" title="Request Feature">Request Feature</button>
519521
</div>
520522
</div>
521523
`).join('');
@@ -949,6 +951,12 @@ document.addEventListener('mouseout', (e) => {
949951
case 'cancelFeedback':
950952
if (bundleId) cancelFeedback(bundleId, actionElement);
951953
break;
954+
case 'reportIssue':
955+
if (bundleId) vscode.postMessage({ type: 'reportIssue', bundleId });
956+
break;
957+
case 'requestFeature':
958+
if (bundleId) vscode.postMessage({ type: 'requestFeature', bundleId });
959+
break;
952960
}
953961
}
954962
});

test/commands/FeedbackCommands.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,9 +232,12 @@ suite('FeedbackCommands', () => {
232232

233233
commands.registerCommands(mockContext);
234234

235-
assert.strictEqual(registerCommandStub.callCount, 2);
235+
assert.strictEqual(registerCommandStub.callCount, 5);
236236
assert.ok(registerCommandStub.calledWith('promptRegistry.feedback'));
237237
assert.ok(registerCommandStub.calledWith('promptRegistry.submitFeedback'));
238+
assert.ok(registerCommandStub.calledWith('promptRegistry.reportIssue'));
239+
assert.ok(registerCommandStub.calledWith('promptRegistry.requestFeature'));
240+
assert.ok(registerCommandStub.calledWith('promptRegistry.retryFeedback'));
238241
});
239242
});
240243

0 commit comments

Comments
 (0)