From 239ddbd362293b5b66a67a435cb1442acac0e581 Mon Sep 17 00:00:00 2001 From: Terence Carrera Date: Wed, 17 Dec 2025 16:06:52 +0800 Subject: [PATCH] feat: Add export functionality for issues and pull requests - Implemented exportIssues and exportPullRequests functions to export data in markdown, CSV, and JSON formats. - Created helper functions for generating markdown and CSV formats for issues and pull requests. - Added showExportDialog for user interaction to select export options. - Implemented saveToFile function to handle file saving. feat: Implement favorites and recent repositories management - Added functionality to add, remove, and check favorites for repositories. - Implemented recent repositories tracking with a limit on the number of recent entries. - Created a quick pick interface to show favorites and recent repositories. feat: Introduce notifications management - Implemented functions to fetch, mark as read, and manage notifications. - Created a NotificationProvider class to display notifications in a tree view. - Added functionality to show notifications in a modal with quick actions. feat: Implement search functionality for repositories, issues, and pull requests - Added searchRepositories, searchIssues, and searchPullRequests functions with filtering options. - Implemented getLabels and getCollaborators functions for additional filtering capabilities. - Created a showFilterQuickPick function for user interaction to select filters. feat: Enhance status bar with shortcuts and dynamic updates - Added default keyboard shortcuts for various commands. - Implemented createStatusBarItem and updateStatusBar functions to manage status bar display. - Created a showStatusMenu function for quick actions related to the extension. --- package.json | 72 +++- src/extension.ts | 697 ++++++++++++++++++++++++++++++---- src/features/export.ts | 229 +++++++++++ src/features/favorites.ts | 172 +++++++++ src/features/notifications.ts | 231 +++++++++++ src/features/search.ts | 265 +++++++++++++ src/features/statusBar.ts | 160 ++++++++ 7 files changed, 1756 insertions(+), 70 deletions(-) create mode 100644 src/features/export.ts create mode 100644 src/features/favorites.ts create mode 100644 src/features/notifications.ts create mode 100644 src/features/search.ts create mode 100644 src/features/statusBar.ts diff --git a/package.json b/package.json index 9487e5b..df44389 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,8 @@ "name": "wizgit-in-vscode", "displayName": "WizGIT in VS Code", "description": "A VS Code extension to interact with WizGIT API for repository management.", - "version": "0.0.3", - "publisher": "TerenceCarrera", + "version": "0.0.4", + "publisher": "Terence Carrera", "icon": "resources/wizgit-marketplace-icon.png", "license": "MIT", "repository": { @@ -105,6 +105,48 @@ "title": "Open Pull Request", "category": "WizGIT", "icon": "$(link-external)" + }, + { + "command": "wizgit-in-vscode.searchRepositories", + "title": "Search Repositories", + "category": "WizGIT", + "icon": "$(telescope)" + }, + { + "command": "wizgit-in-vscode.searchIssues", + "title": "Search Issues", + "category": "WizGIT", + "icon": "$(search)" + }, + { + "command": "wizgit-in-vscode.searchPullRequests", + "title": "Search Pull Requests", + "category": "WizGIT", + "icon": "$(search)" + }, + { + "command": "wizgit-in-vscode.openRecentRepositories", + "title": "Open Recent Repositories", + "category": "WizGIT", + "icon": "$(history)" + }, + { + "command": "wizgit-in-vscode.exportIssues", + "title": "Export Issues", + "category": "WizGIT", + "icon": "$(save)" + }, + { + "command": "wizgit-in-vscode.exportPullRequests", + "title": "Export Pull Requests", + "category": "WizGIT", + "icon": "$(save)" + }, + { + "command": "wizgit-in-vscode.showNotifications", + "title": "Show Notifications", + "category": "WizGIT", + "icon": "$(bell)" } ], "menus": { @@ -159,6 +201,32 @@ } ] }, + "keybindings": [ + { + "command": "wizgit-in-vscode.searchRepositories", + "key": "ctrl+shift+g r", + "mac": "cmd+shift+g r", + "when": "wizgit:enabled" + }, + { + "command": "wizgit-in-vscode.searchIssues", + "key": "ctrl+shift+g i", + "mac": "cmd+shift+g i", + "when": "wizgit:enabled" + }, + { + "command": "wizgit-in-vscode.searchPullRequests", + "key": "ctrl+shift+g p", + "mac": "cmd+shift+g p", + "when": "wizgit:enabled" + }, + { + "command": "wizgit-in-vscode.openRecentRepositories", + "key": "ctrl+shift+g t", + "mac": "cmd+shift+g t", + "when": "wizgit:enabled" + } + ], "configuration": { "title": "WizGIT", "properties": { diff --git a/src/extension.ts b/src/extension.ts index 4e0b1aa..0fb76bc 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -5,6 +5,13 @@ import * as fs from 'fs'; import * as child_process from 'child_process'; import { promisify } from 'util'; +// Import new feature modules +import { searchRepositories, searchIssues, searchPullRequests, showFilterQuickPick } from './features/search'; +import { showRepositoryQuickPick, addToRecent } from './features/favorites'; +import { createStatusBarItem, registerStatusBarClickHandler } from './features/statusBar'; +import { getUnreadNotifications, showNotificationModal } from './features/notifications'; +import { exportIssues, exportPullRequests, showExportDialog, saveToFile } from './features/export'; + const execAsync = promisify(child_process.exec); // Interfaces for API responses @@ -838,6 +845,322 @@ export function activate(context: vscode.ExtensionContext) { vscode.window.showInformationMessage('WizGIT credentials cleared successfully.'); }); + // Register Search Repositories command + let searchReposDisposable = vscode.commands.registerCommand('wizgit-in-vscode.searchRepositories', async () => { + const credentials = await getStoredCredentials(context); + if (!credentials) { + vscode.window.showErrorMessage('Please configure WizGIT credentials first'); + return; + } + + const query = await vscode.window.showInputBox({ + prompt: 'Search repositories', + ignoreFocusOut: true + }); + + if (query) { + try { + const result = await searchRepositories(credentials.apiEndpoint, credentials.token, query); + const repos = result.data || []; + + if (repos.length === 0) { + vscode.window.showInformationMessage('No repositories found'); + return; + } + + const selected = await vscode.window.showQuickPick( + repos.map((r: any) => ({ + label: r.name, + description: r.full_name, + detail: r.description, + data: r + })), + { placeHolder: 'Select a repository' } + ) as any; + + if (selected) { + await addToRecent(context, `${selected.data.id}`, selected.data.name, + selected.data.full_name.split('/')[0], selected.data.full_name.split('/')[1]); + vscode.window.showInformationMessage(`Selected: ${selected.data.full_name}`); + + // Prompt user to open in VS Code or Browser + const action = await vscode.window.showInformationMessage( + `Open repository ${selected.data.name} in:`, + 'VS Code', + 'Browser' + ); + + if (action === 'VS Code') { + await cloneAndOpenRepository(selected.data.clone_url, selected.data.name); + } else if (action === 'Browser') { + vscode.env.openExternal(vscode.Uri.parse(selected.data.html_url)); + } + } + } catch (error) { + vscode.window.showErrorMessage(`Search failed: ${error}`); + } + } + }); + + // Register Search Issues command + let searchIssuesDisposable = vscode.commands.registerCommand('wizgit-in-vscode.searchIssues', async () => { + const credentials = await getStoredCredentials(context); + if (!credentials) { + vscode.window.showErrorMessage('Please configure WizGIT credentials first'); + return; + } + + const repoInfo = await getWorkspaceRepoInfo(); + if (!repoInfo) { + vscode.window.showErrorMessage('No WizGIT repository detected in workspace'); + return; + } + + try { + const filterOptions = await showFilterQuickPick(credentials.apiEndpoint, credentials.token, + repoInfo.owner, repoInfo.repo, 'issues'); + + const searchQuery = filterOptions?.state ? `state:${filterOptions.state}` : ''; + + const result = await searchIssues(credentials.apiEndpoint, credentials.token, + repoInfo.owner, repoInfo.repo, { + query: searchQuery, + ...filterOptions, + page: 1, + limit: 50 + }); + + const issues = result.data || []; + if (issues.length === 0) { + vscode.window.showInformationMessage('No issues found'); + return; + } + + const selected = await vscode.window.showQuickPick( + issues.map((i: any) => ({ + label: `#${i.number} - ${i.title}`, + description: i.state, + detail: i.body?.substring(0, 50), + data: i + })), + { placeHolder: 'Select an issue' } + ) as any; + + if (selected) { + await openIssueViewer(context, selected.data, `${repoInfo.owner}/${repoInfo.repo}`); + } + } catch (error) { + vscode.window.showErrorMessage(`Search failed: ${error}`); + } + }); + + // Register Search Pull Requests command + let searchPRsDisposable = vscode.commands.registerCommand('wizgit-in-vscode.searchPullRequests', async () => { + const credentials = await getStoredCredentials(context); + if (!credentials) { + vscode.window.showErrorMessage('Please configure WizGIT credentials first'); + return; + } + + const repoInfo = await getWorkspaceRepoInfo(); + if (!repoInfo) { + vscode.window.showErrorMessage('No WizGIT repository detected in workspace'); + return; + } + + try { + const filterOptions = await showFilterQuickPick(credentials.apiEndpoint, credentials.token, + repoInfo.owner, repoInfo.repo, 'pulls'); + + const result = await searchPullRequests(credentials.apiEndpoint, credentials.token, + repoInfo.owner, repoInfo.repo, { + query: '', + ...filterOptions, + page: 1, + limit: 50 + }); + + const prs = result.data || []; + if (prs.length === 0) { + vscode.window.showInformationMessage('No pull requests found'); + return; + } + + const selected = await vscode.window.showQuickPick( + prs.map((p: any) => ({ + label: `#${p.number} - ${p.title}`, + description: p.state, + detail: `${p.head.ref} → ${p.base.ref}`, + data: p + })), + { placeHolder: 'Select a pull request' } + ) as any; + + if (selected) { + await openPullRequestViewer(context, selected.data, `${repoInfo.owner}/${repoInfo.repo}`); + } + } catch (error) { + vscode.window.showErrorMessage(`Search failed: ${error}`); + } + }); + + // Register Open Recent Repositories command + let recentReposDisposable = vscode.commands.registerCommand('wizgit-in-vscode.openRecentRepositories', async () => { + const selected = await showRepositoryQuickPick(context); + if (selected) { + // Prompt user to open in VS Code or Browser + const action = await vscode.window.showInformationMessage( + `Open repository ${selected.name} in:`, + 'VS Code', + 'Browser' + ); + if (action === 'VS Code' && selected.cloneUrl) { + // Clone the repository first, then open it + await cloneAndOpenRepository(selected.cloneUrl, selected.name); + } else if (action === 'Browser' && selected.htmlUrl) { + vscode.env.openExternal(vscode.Uri.parse(selected.htmlUrl)); + } + } + }); + + // Register Export Issues command + let exportIssuesDisposable = vscode.commands.registerCommand('wizgit-in-vscode.exportIssues', async () => { + const credentials = await getStoredCredentials(context); + if (!credentials) { + vscode.window.showErrorMessage('Please configure WizGIT credentials first'); + return; + } + + const repoInfo = await getWorkspaceRepoInfo(); + if (!repoInfo) { + vscode.window.showErrorMessage('No WizGIT repository detected in workspace'); + return; + } + + try { + const result = await searchIssues(credentials.apiEndpoint, credentials.token, + repoInfo.owner, repoInfo.repo, { + query: '', + page: 1, + limit: 100 + }); + + const issues = result.data || []; + if (issues.length === 0) { + vscode.window.showInformationMessage('No issues found to export'); + return; + } + + const options = await showExportDialog('issues'); + if (!options) return; + + const content = await exportIssues(issues, options); + if (content) { + const filename = `issues-export-${repoInfo.repo}-${Date.now()}.${options.format}`; + await saveToFile(content, filename, options.format); + } + } catch (error) { + vscode.window.showErrorMessage(`Export failed: ${error}`); + } + }); + + // Register Export Pull Requests command + let exportPRsDisposable = vscode.commands.registerCommand('wizgit-in-vscode.exportPullRequests', async () => { + const credentials = await getStoredCredentials(context); + if (!credentials) { + vscode.window.showErrorMessage('Please configure WizGIT credentials first'); + return; + } + + const repoInfo = await getWorkspaceRepoInfo(); + if (!repoInfo) { + vscode.window.showErrorMessage('No WizGIT repository detected in workspace'); + return; + } + + try { + const result = await searchPullRequests(credentials.apiEndpoint, credentials.token, + repoInfo.owner, repoInfo.repo, { + query: '', + page: 1, + limit: 100 + }); + + const prs = result.data || []; + if (prs.length === 0) { + vscode.window.showInformationMessage('No pull requests found to export'); + return; + } + + const options = await showExportDialog('pullRequests'); + if (!options) return; + + const content = await exportPullRequests(prs, options); + if (content) { + const filename = `prs-export-${repoInfo.repo}-${Date.now()}.${options.format}`; + await saveToFile(content, filename, options.format); + } + } catch (error) { + vscode.window.showErrorMessage(`Export failed: ${error}`); + } + }); + + // Register Show Notifications command + let notificationsDisposable = vscode.commands.registerCommand('wizgit-in-vscode.showNotifications', async () => { + const credentials = await getStoredCredentials(context); + if (!credentials) { + vscode.window.showErrorMessage('Please configure WizGIT credentials first'); + return; + } + + try { + const notifications = await getUnreadNotifications(credentials.apiEndpoint, credentials.token); + + if (notifications.length === 0) { + vscode.window.showInformationMessage('No unread notifications'); + return; + } + + const selected = await vscode.window.showQuickPick( + notifications.map(n => ({ + label: `${n.subject.title}`, + description: n.repository.name, + detail: n.reason, + data: n + })), + { placeHolder: 'Select a notification' } + ); + + if (selected) { + await showNotificationModal(selected.data); + } + } catch (error) { + vscode.window.showErrorMessage(`Failed to fetch notifications: ${error}`); + } + }); + + // Create and register status bar (subscriptions handled by registerStatusBarClickHandler) + createStatusBarItem(context); + registerStatusBarClickHandler(context, async (action) => { + switch (action) { + case 'search-repos': + await vscode.commands.executeCommand('wizgit-in-vscode.searchRepositories'); + break; + case 'search-issues': + await vscode.commands.executeCommand('wizgit-in-vscode.searchIssues'); + break; + case 'search-prs': + await vscode.commands.executeCommand('wizgit-in-vscode.searchPullRequests'); + break; + case 'recent-repos': + await vscode.commands.executeCommand('wizgit-in-vscode.openRecentRepositories'); + break; + case 'configure': + await vscode.commands.executeCommand('wizgit-in-vscode.configure'); + break; + } + }); + context.subscriptions.push( disposable, createIssueDisposable, @@ -847,7 +1170,14 @@ export function activate(context: vscode.ExtensionContext) { refreshDisposable, cloneDisposable, openIssueDisposable, - openPRDisposable + openPRDisposable, + searchReposDisposable, + searchIssuesDisposable, + searchPRsDisposable, + recentReposDisposable, + exportIssuesDisposable, + exportPRsDisposable, + notificationsDisposable ); } @@ -1011,6 +1341,8 @@ async function createIssueWebview(context: vscode.ExtensionContext) { panel.webview.html = getIssueWebviewContent(credentials, repoInfo, branches); panel.webview.onDidReceiveMessage(async message => { + console.log('Issue webview received message:', message.command, message.data); + switch (message.command) { case 'createIssue': await handleCreateIssue(message.data, panel, context); @@ -1025,6 +1357,11 @@ async function createIssueWebview(context: vscode.ExtensionContext) { ]); panel.webview.html = getIssueWebviewContent(updatedCredentials, updatedRepoInfo, updatedBranches); break; + case 'openUrl': + if (message.url) { + vscode.env.openExternal(vscode.Uri.parse(message.url)); + } + break; } }, undefined, context.subscriptions); } @@ -1168,11 +1505,8 @@ function getIssueWebviewContent(credentials?: { apiEndpoint: string, token: stri
- - -

Issue Information

+
-
@@ -1254,27 +1588,83 @@ function getIssueWebviewContent(credentials?: { apiEndpoint: string, token: stri