Skip to content

Commit

Permalink
issue management command
Browse files Browse the repository at this point in the history
  • Loading branch information
gargadobe committed Jul 18, 2024
1 parent cc66342 commit c8e7d92
Show file tree
Hide file tree
Showing 20 changed files with 590 additions and 62 deletions.
11 changes: 11 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@
"npm: watch-tests"
],
"problemMatcher": []
},
{
"label": "webpack: watch",
"type": "shell",
"command": "npm",
"args": [
"run",
"watch"
], // Assuming you have a "watch" script in your package.json
"isBackground": true,
"problemMatcher": "$tsc-watch"
}
]
}
316 changes: 269 additions & 47 deletions package-lock.json

Large diffs are not rendered by default.

14 changes: 10 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
{
"name": "docs",
"description": "Information About AEM Edge Delivery Services"
},
{
"name": "issues",
"description": "Issues management for AEM Edge Delivery Services"
}
]
}
Expand All @@ -69,12 +73,13 @@
"build": "webpack --mode production"
},
"devDependencies": {
"@octokit/rest": "^20.1.1",
"@types/jsdom": "^21.1.6",
"@types/mocha": "^10.0.6",
"@types/node": "^20.5.9",
"@types/vscode": "1.90.0",
"@typescript-eslint/eslint-plugin": "^7.11.0",
"@typescript-eslint/parser": "^7.11.0",
"@typescript-eslint/eslint-plugin": "^7.16.1",
"@typescript-eslint/parser": "^7.16.1",
"@vscode/prompt-tsx": "^0.2.3-alpha",
"@vscode/test-cli": "^0.0.9",
"@vscode/test-electron": "^2.4.0",
Expand All @@ -84,9 +89,10 @@
"tslint": "^6.1.3",
"typescript": "^5.5.2",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4"
"webpack-cli": "^5.1.4",
"simple-git": "^3.24.0"
},
"dependencies": {
"jsdom": "^24.1.0"
}
}
}
1 change: 1 addition & 0 deletions src/aem.commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export enum AEM_COMMANDS {
// ENHANCE = "enhance", //TODO
COLLECION = "collection",
DOCS = "docs",
ISSUES = "issues",
}
4 changes: 4 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const PROCESS_COPILOT_CREATE_CMD = "aem.createFiles";
export const PROCESS_COPILOT_CREATE_CMD_TITLE = "Create Block";
export const COPILOT_CREATE_CMD = "AEM block with base template";


// Github Repository Details
export const OWNER = 'adobe';
export const REPO = 'aem-block-collection';
Expand All @@ -17,6 +18,9 @@ export const MODEL_VENDOR: string = "copilot";
export const LANGUAGE_MODEL_ID: string = "gpt-3.5-turbo"; // Use faster model. Alternative is 'gpt-4', which is slower but more powerful
export const MODEL_SELECTOR: vscode.LanguageModelChatSelector = { vendor: MODEL_VENDOR, family: LANGUAGE_MODEL_ID };

// Issue Management Constants
export const FETCH_ISSUE_DETAIL_CMD = "Fetch Issue Details Command";


export const GREETINGS = [
"Let me think how I can help you... 🤔",
Expand Down
15 changes: 13 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as vscode from "vscode";
import { AEM_COMMANDS as commands } from "./aem.commands";
import { AEM_COMMANDS, AEM_COMMANDS as commands } from "./aem.commands";
import {
createCmdHandler,
} from "./handlers/block.create";
Expand All @@ -8,11 +8,13 @@ import { handleDocsCommand } from "./handlers/block.docs";

import {
AEM_COMMAND_ID,
FETCH_ISSUE_DETAIL_CMD,
PROCESS_COPILOT_CREATE_CMD,
} from "./constants";
import { createFolderAndFiles } from "./utils/fileHandler";
import { fetchBlock } from "./handlers/block.collections";
import { getRandomGreeting } from "./utils/helpers";
import { handleIssueManagement } from "./handlers/issueManagement.handler";

interface IAemChatResult extends vscode.ChatResult {
metadata: {
Expand All @@ -37,14 +39,20 @@ export function activate(context: vscode.ExtensionContext) {
cmdResult = await createCmdHandler(request, stream, token);
} else if (request.command === commands.COLLECION) {
cmdResult = await fetchBlock(request, stream, token, context);
} else if (request.command === commands.ISSUES) {
cmdResult = await handleIssueManagement(request, stream, token);
} else {
cmdResult = await handleDocsCommand(request, stream, token);
}
} catch (err) {
handleError(err, stream);
}

return cmdResult.metadata;
return {
metadata: {
command: request.command || "",
},
};
};

const aem = vscode.chat.createChatParticipant(AEM_COMMAND_ID, handler);
Expand Down Expand Up @@ -76,6 +84,9 @@ export function activate(context: vscode.ExtensionContext) {
await createFolderAndFiles(filesToCreate);
}
),
vscode.commands.registerCommand(FETCH_ISSUE_DETAIL_CMD, async (githubIssue) => {
vscode.commands.executeCommand(`workbench.action.chat.open`, `@${AEM_COMMAND_ID} /${commands.ISSUES} fetch me details of issue #${githubIssue.number}`);
})
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/handlers/block.create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export async function createCmdHandler(
const promptProps = {
userQuery: request.prompt,
};
const chatResponse = await getChatResponse(CreateBlockPrompt, promptProps, token, );
const chatResponse = await getChatResponse(CreateBlockPrompt, promptProps, token);
let resultJsonStr = "";
for await (const fragment of chatResponse.text) {
resultJsonStr += fragment;
Expand Down
2 changes: 1 addition & 1 deletion src/handlers/block.docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AEM_COMMANDS as commands } from "../aem.commands";
import { MODEL_SELECTOR } from '../constants';
import { DocsPrompt } from '../prompts/block.docs';
import { getChatResponse } from '../utils/helpers';
import { DocsPromptProps } from '../interfaces/promptInterfaces';
import { DocsPromptProps } from '../interfaces/prompt.Interfaces';

const INDEX_URL: string = "https://www.aem.live/docpages-index.json";

Expand Down
2 changes: 1 addition & 1 deletion src/handlers/block.info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as vscode from "vscode";
import { AEM_COMMANDS as commands } from "../aem.commands";
import { AEMInfoPrompt } from "../prompts/eds.info.prompt";
import { getChatResponse } from "../utils/helpers";
import { PromptProps } from "../interfaces/promptInterfaces";
import { PromptProps } from "../interfaces/prompt.Interfaces";

export async function infoCmdHandler(
request: vscode.ChatRequest,
Expand Down
140 changes: 140 additions & 0 deletions src/handlers/issueManagement.handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import * as vscode from "vscode";
import { AEM_COMMANDS as commands } from "../aem.commands";
import { Comment, Issue } from "../interfaces/issueManagement.interfaces";
import { fetchAllIssues, fetchIssueDetailsByNumber, fetchLatestIssueDetails, getGitHubClient } from "../utils/github.helper";
import { FETCH_ISSUE_DETAIL_CMD } from "../constants";
import { IssuesManagePrompt } from "../prompts/issueManagement.prompt";
import { IssuesManagePromptProps } from "../interfaces/prompt.Interfaces";
import { getChatResponse } from "../utils/helpers";

// Extracts owner and repo name from the workspace's Git configuration
async function extractRepoDetailsFromWorkspace(): Promise<{ owner: string; repoName: string } | null> {
const gitExtension = vscode.extensions.getExtension('vscode.git')?.exports;
if (!gitExtension) {
vscode.window.showErrorMessage('Unable to load Git extension');
return null;
}

const api = gitExtension.getAPI(1);
if (api.repositories.length === 0) {
vscode.window.showInformationMessage('No Git repositories found');
return null;
}

const repo = api.repositories[0];
const remotes = repo.state.remotes;
if (remotes.length === 0) {
vscode.window.showInformationMessage('No remotes found');
return null;
}

const remoteUrl = remotes[0].fetchUrl;
const match = remoteUrl?.match(/github\.com[:/](.+?)\/(.+?)(?:\.git)?$/);
if (!match) {
vscode.window.showErrorMessage('Unable to parse GitHub repository URL');
return null;
}

return { owner: match[1], repoName: match[2] };
}


function streamIssueDetails(
stream: vscode.ChatResponseStream,
issue: any,
comments: Comment[]
) {
stream.progress(`Issue "${issue.title}" loaded.`);
stream.markdown(`Issue: **${issue.title}**\n\n`);
stream.markdown(issue.body?.replaceAll("\n", "\n> ") + "");
if (comments?.length > 0) {
stream.markdown("\n\n_Comments_\n");
comments?.map((comment) =>
stream.markdown(`\n> ${comment.body?.replaceAll("\n", "\n> ") + ""}\n`)
);
}
stream.markdown("\n\n----\n\n");
}

async function streamCopilotResponse(stream: vscode.ChatResponseStream, issueDetails: Issue, promptProps: IssuesManagePromptProps, token: vscode.CancellationToken) {
stream.progress(`Copilot suggestion....`);
try {
const issueDetailsStr = `The issue to work on has the title: "${issueDetails?.title}" and the description: ${issueDetails?.body}. Use that information to give better answer for the following user query.` +
(issueDetails?.comments && issueDetails?.comments?.length > 0
? `Do also regard the comments: ${issueDetails?.comments
?.map((comment) => comment.body)
.join("\n\n") + ""
}`
: "");
promptProps.issueDetails = issueDetailsStr;
const chatResponse = await getChatResponse(IssuesManagePrompt, promptProps, token,);
for await (const fragment of chatResponse.text) {
stream.markdown(fragment);
}
} catch (error) {
stream.markdown("Some Network issues. Please try again..");
}
}

// Main handler for issue management
export async function handleIssueManagement(request: vscode.ChatRequest, stream: vscode.ChatResponseStream, token: vscode.CancellationToken): Promise<{ metadata: { command: string } }> {
const userQuery = request.prompt.toLowerCase();
const workspaceDetails = await extractRepoDetailsFromWorkspace();
if (!workspaceDetails) {
stream.markdown("Repository details not found.");
return { metadata: { command: commands.ISSUES } };
}

let promptProps: IssuesManagePromptProps = {
userQuery: request.prompt,
issueDetails: "",
};

const octokit = await getGitHubClient();

// Determine action based on user query
if (userQuery.includes("latest issue")) {
const issueDetails = await fetchLatestIssueDetails(workspaceDetails.owner, workspaceDetails.repoName, octokit);
if (!issueDetails) {
stream.markdown("Latest issue details not found.");
return { metadata: { command: commands.ISSUES } };
}
streamIssueDetails(stream, issueDetails, issueDetails.comments);
await streamCopilotResponse(stream, issueDetails, promptProps, token);
} else if (userQuery.match(/issue #(\d+)/)) {
const match = userQuery.match(/issue #(\d+)/);
const issueNumber = match ? parseInt(match[1]) : null;
if (!issueNumber) {
stream.markdown("Issue number not found.");
return { metadata: { command: commands.ISSUES } };
}
const issueDetails = await fetchIssueDetailsByNumber(workspaceDetails.owner, workspaceDetails.repoName, issueNumber, octokit);
if (!issueDetails) {
stream.markdown(`Details for issue #${issueNumber} not found.`);
return { metadata: { command: commands.ISSUES } };
}
streamIssueDetails(stream, issueDetails, issueDetails.comments);
await streamCopilotResponse(stream, issueDetails, promptProps, token);
} else if (userQuery.includes("list")) {
const issuesList = await fetchAllIssues(workspaceDetails.owner, workspaceDetails.repoName, octokit, 5);
if (!issuesList || issuesList.length === 0) {
stream.markdown("No issues found.");
return { metadata: { command: commands.ISSUES } };
}
stream.markdown("Here are the latest issues:\n");
issuesList.forEach((issue: any) => {
stream.button({
command: FETCH_ISSUE_DETAIL_CMD,
title: `${issue.number}: ${issue.title}`,
tooltip: vscode.l10n.t(`Fetch details for Issue #${issue.number}`),
arguments: [issue],

});
});
} else {
stream.markdown("I'm not sure how to help with that. You can ask for the 'latest issue', 'list all issues', or about a specific 'issue #number'.");
return { metadata: { command: commands.ISSUES } };
}

return { metadata: { command: commands.ISSUES } };
}
File renamed without changes.
11 changes: 11 additions & 0 deletions src/interfaces/issueManagement.interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface Comment {
id: number;
url: string;
body?: string | undefined;
}

export interface Issue {
title: string;
body: string;
comments: Comment[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,11 @@ export interface DocsPromptProps extends PromptProps {
*/
export interface CreatePromptState {
projectStyleCSS: string;
}

/**
* Prompt state for the issues manage prompt
*/
export interface IssuesManagePromptProps extends PromptProps {
issueDetails: string;
}
2 changes: 1 addition & 1 deletion src/prompts/block.docs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from '@vscode/prompt-tsx';

import * as prompts from "./templates/default";
import { DocsPromptProps } from '../interfaces/promptInterfaces';
import { DocsPromptProps } from '../interfaces/prompt.Interfaces';

export class DocsPrompt extends PromptElement<DocsPromptProps, any> {

Expand Down
2 changes: 1 addition & 1 deletion src/prompts/create.block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {

import * as prompts from "./templates/create.block";
import { getProjectLevelStyles } from '../utils/helpers';
import { CreatePromptState, PromptProps } from '../interfaces/promptInterfaces';
import { CreatePromptState, PromptProps } from '../interfaces/prompt.Interfaces';

export class CreateBlockPrompt extends PromptElement<PromptProps, CreatePromptState> {

Expand Down
4 changes: 2 additions & 2 deletions src/prompts/eds.info.prompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {
UserMessage,
} from '@vscode/prompt-tsx';

import * as prompts from "./templates/eds.info.prompt";
import { PromptProps } from '../interfaces/promptInterfaces';
import * as prompts from "./templates/eds.info";
import { PromptProps } from '../interfaces/prompt.Interfaces';

export class AEMInfoPrompt extends PromptElement<PromptProps, any> {

Expand Down
27 changes: 27 additions & 0 deletions src/prompts/issueManagement.prompt.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
PromptElement,
PromptSizing,
UserMessage,
} from '@vscode/prompt-tsx';

import { IssuesManagePromptProps } from '../interfaces/prompt.Interfaces';

export const SYSTEM_MESSAGE = `You are a software product owner and you help your developers providing additional information
for working on current software development task from github issue details.`;

export class IssuesManagePrompt extends PromptElement<IssuesManagePromptProps, any> {

render(state: void, sizing: PromptSizing) {
return (
<>
<UserMessage>{SYSTEM_MESSAGE}</UserMessage>
<UserMessage>
Here are the Github issue details<br />
{this.props.issueDetails} <br />
Explain the issue details to the developer and probably provide some additional information.<br />
Provide the response in nice markdown format to the user.
</UserMessage>
</>
);
}
}
File renamed without changes.
Loading

0 comments on commit c8e7d92

Please sign in to comment.