Update report an issue action to open github issue with autogenerated issue template (#166)
Signed-off-by: Sanjula Ganepola <sanjulagane@gmail.com>
This commit is contained in:
20
.github/ISSUE_TEMPLATE/1-bug_report.yml
vendored
20
.github/ISSUE_TEMPLATE/1-bug_report.yml
vendored
@@ -3,6 +3,10 @@ description: Report a bug or issue.
|
||||
labels:
|
||||
- 'bug'
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
⭐ Most of the content for this bug report can be automatically generated by selecting `Help and Support` -> `Report an Issue` from any GitHub Local Actions view. ⭐
|
||||
- type: input
|
||||
id: github-local-actions-version
|
||||
attributes:
|
||||
@@ -76,14 +80,6 @@ body:
|
||||
render: sh
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: Bug Description
|
||||
description: |
|
||||
Describe the bug you encountered and share all steps to reproduce it.
|
||||
placeholder: |
|
||||
Bug description
|
||||
- type: textarea
|
||||
id: act-bug-report
|
||||
attributes:
|
||||
@@ -104,3 +100,11 @@ body:
|
||||
render: yml
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: Bug Description
|
||||
description: |
|
||||
Describe the bug you encountered and share all steps to reproduce it.
|
||||
placeholder: |
|
||||
Bug description
|
||||
@@ -13,7 +13,7 @@
|
||||
"repository": {
|
||||
"url": "https://github.com/SanjulaGanepola/github-local-actions"
|
||||
},
|
||||
"homepage": "https://github.com/SanjulaGanepola/github-local-actions/blob/main/README.md",
|
||||
"homepage": "https://sanjulaganepola.github.io/github-local-actions-docs",
|
||||
"bugs": {
|
||||
"url": "https://github.com/SanjulaGanepola/github-local-actions/issues"
|
||||
},
|
||||
|
||||
45
src/act.ts
45
src/act.ts
@@ -9,7 +9,7 @@ import { ConfigurationManager, Platform, Section } from "./configurationManager"
|
||||
import { componentsTreeDataProvider, historyTreeDataProvider } from './extension';
|
||||
import { HistoryManager, HistoryStatus } from './historyManager';
|
||||
import { SecretManager } from "./secretManager";
|
||||
import { Mode, SettingsManager } from './settingsManager';
|
||||
import { Mode, Settings, SettingsManager } from './settingsManager';
|
||||
import { StorageKey, StorageManager } from './storageManager';
|
||||
import { Utils } from "./utils";
|
||||
import { Job, Workflow, WorkflowsManager } from "./workflowsManager";
|
||||
@@ -576,6 +576,30 @@ export class Act {
|
||||
return path.join(cacheHomeDir, ...paths);
|
||||
}
|
||||
|
||||
async buildActCommand(settings: Settings, options: string[]) {
|
||||
const userOptions: string[] = [
|
||||
...settings.secrets.map(secret => `${Option.Secret} ${secret.key}`),
|
||||
(settings.secretFiles.length > 0 ? `${Option.SecretFile} "${settings.secretFiles[0].path}"` : `${Option.SecretFile} ""`),
|
||||
...settings.variables.map(variable => `${Option.Var} ${variable.key}="${Utils.escapeSpecialCharacters(variable.value)}"`),
|
||||
(settings.variableFiles.length > 0 ? `${Option.VarFile} "${settings.variableFiles[0].path}"` : `${Option.VarFile} ""`),
|
||||
...settings.inputs.map(input => `${Option.Input} ${input.key}="${Utils.escapeSpecialCharacters(input.value)}"`),
|
||||
(settings.inputFiles.length > 0 ? `${Option.InputFile} "${settings.inputFiles[0].path}"` : `${Option.InputFile} ""`),
|
||||
...settings.runners.map(runner => `${Option.Platform} ${runner.key}="${Utils.escapeSpecialCharacters(runner.value)}"`),
|
||||
(settings.payloadFiles.length > 0 ? `${Option.EventPath} "${settings.payloadFiles[0].path}"` : `${Option.EventPath} ""`),
|
||||
...settings.options.map(option => option.path ? `--${option.name}${option.default && ['true', 'false'].includes(option.default) ? "=" : " "}"${Utils.escapeSpecialCharacters(option.path)}"` : `--${option.name}`)
|
||||
];
|
||||
|
||||
const actCommand = Act.getActCommand();
|
||||
const executionCommand = `${actCommand} ${Option.Json} ${Option.Verbose} ${options.join(' ')} ${userOptions.join(' ')}`;
|
||||
const displayCommand = `${actCommand} ${options.join(' ')} ${userOptions.join(' ')}`;
|
||||
|
||||
return {
|
||||
userOptions,
|
||||
executionCommand,
|
||||
displayCommand
|
||||
};
|
||||
}
|
||||
|
||||
async runCommand(commandArgs: CommandArgs) {
|
||||
// Check if required components are ready
|
||||
// const unreadyComponents = await this.componentsManager.getUnreadyComponents();
|
||||
@@ -624,23 +648,8 @@ export class Act {
|
||||
} catch (error: any) { }
|
||||
|
||||
// Build command with settings
|
||||
const actCommand = Act.getActCommand();
|
||||
const settings = await this.settingsManager.getSettings(workspaceFolder, true);
|
||||
|
||||
const userOptions: string[] = [
|
||||
...settings.secrets.map(secret => `${Option.Secret} ${secret.key}`),
|
||||
(settings.secretFiles.length > 0 ? `${Option.SecretFile} "${settings.secretFiles[0].path}"` : `${Option.SecretFile} ""`),
|
||||
...settings.variables.map(variable => `${Option.Var} ${variable.key}="${Utils.escapeSpecialCharacters(variable.value)}"`),
|
||||
(settings.variableFiles.length > 0 ? `${Option.VarFile} "${settings.variableFiles[0].path}"` : `${Option.VarFile} ""`),
|
||||
...settings.inputs.map(input => `${Option.Input} ${input.key}="${Utils.escapeSpecialCharacters(input.value)}"`),
|
||||
(settings.inputFiles.length > 0 ? `${Option.InputFile} "${settings.inputFiles[0].path}"` : `${Option.InputFile} ""`),
|
||||
...settings.runners.map(runner => `${Option.Platform} ${runner.key}="${Utils.escapeSpecialCharacters(runner.value)}"`),
|
||||
(settings.payloadFiles.length > 0 ? `${Option.EventPath} "${settings.payloadFiles[0].path}"` : `${Option.EventPath} ""`),
|
||||
...settings.options.map(option => option.path ? `--${option.name}${option.default && ['true', 'false'].includes(option.default) ? "=" : " "}"${Utils.escapeSpecialCharacters(option.path)}"` : `--${option.name}`)
|
||||
];
|
||||
|
||||
const command = `${actCommand} ${Option.Json} ${Option.Verbose} ${commandArgs.options.join(' ')} ${userOptions.join(' ')}`;
|
||||
const displayCommand = `${actCommand} ${commandArgs.options.join(' ')} ${userOptions.join(' ')}`;
|
||||
const { userOptions, executionCommand, displayCommand } = await this.buildActCommand(settings, commandArgs.options);
|
||||
|
||||
// Execute task
|
||||
const taskExecution = await tasks.executeTask({
|
||||
@@ -854,7 +863,7 @@ export class Act {
|
||||
};
|
||||
|
||||
const exec = childProcess.spawn(
|
||||
command,
|
||||
executionCommand,
|
||||
{
|
||||
cwd: commandArgs.path,
|
||||
shell: shell,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as childProcess from "child_process";
|
||||
import { commands, env, extensions, QuickPickItemKind, ShellExecution, TaskGroup, TaskPanelKind, TaskRevealKind, tasks, TaskScope, ThemeIcon, Uri, window } from "vscode";
|
||||
import { Act } from "./act";
|
||||
import { Act, Option } from "./act";
|
||||
import { ConfigurationManager, Platform, Section } from "./configurationManager";
|
||||
import { act, componentsTreeDataProvider } from "./extension";
|
||||
import ComponentsTreeDataProvider from "./views/components/componentsTreeDataProvider";
|
||||
@@ -39,7 +39,7 @@ export class ComponentsManager {
|
||||
async getComponents(): Promise<Component<CliStatus | ExtensionStatus>[]> {
|
||||
const components: Component<CliStatus | ExtensionStatus>[] = [];
|
||||
|
||||
const actCliInfo = await this.getCliInfo(`${Act.getActCommand()} --version`, ComponentsManager.actVersionRegExp, false, false);
|
||||
const actCliInfo = await this.getCliInfo(`${Act.getActCommand()} ${Option.Version}`, ComponentsManager.actVersionRegExp, false, false);
|
||||
components.push({
|
||||
name: 'nektos/act',
|
||||
icon: 'terminal',
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as vscode from 'vscode';
|
||||
import { commands, env, TreeCheckboxChangeEvent, Uri, window, workspace } from 'vscode';
|
||||
import { Act } from './act';
|
||||
import { ConfigurationManager } from './configurationManager';
|
||||
import { IssueHandler } from './issueHandler';
|
||||
import ComponentsTreeDataProvider from './views/components/componentsTreeDataProvider';
|
||||
import { DecorationProvider } from './views/decorationProvider';
|
||||
import { GithubLocalActionsTreeItem } from './views/githubLocalActionsTreeItem';
|
||||
@@ -68,10 +69,10 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
window.registerFileDecorationProvider(decorationProvider),
|
||||
workflowsFileWatcher,
|
||||
commands.registerCommand('githubLocalActions.viewDocumentation', async () => {
|
||||
await env.openExternal(Uri.parse('https://nektosact.com'));
|
||||
await env.openExternal(Uri.parse('https://sanjulaganepola.github.io/github-local-actions-docs'));
|
||||
}),
|
||||
commands.registerCommand('githubLocalActions.reportAnIssue', async () => {
|
||||
await env.openExternal(Uri.parse('https://github.com/SanjulaGanepola/github-local-actions/issues'));
|
||||
await IssueHandler.openBugReport(context);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface Response<T> {
|
||||
}
|
||||
|
||||
export interface GithubRepository {
|
||||
remoteOriginUrl: string,
|
||||
owner: string,
|
||||
repo: string
|
||||
}
|
||||
@@ -24,7 +25,7 @@ export interface GithubVariable {
|
||||
}
|
||||
|
||||
export class GitHubManager {
|
||||
async getRepository(workspaceFolder: WorkspaceFolder, command: string, args: any[]): Promise<GithubRepository | undefined> {
|
||||
async getRepository(workspaceFolder: WorkspaceFolder, suppressNotFoundErrors: boolean, tryAgainOptions?: { command: string, args: any[] }): Promise<GithubRepository | undefined> {
|
||||
const gitApi = extensions.getExtension<GitExtension>('vscode.git')?.exports.getAPI(1);
|
||||
if (gitApi) {
|
||||
if (gitApi.state === 'initialized') {
|
||||
@@ -38,19 +39,25 @@ export class GitHubManager {
|
||||
const parsedParentPath = path.parse(parsedPath.dir);
|
||||
|
||||
return {
|
||||
remoteOriginUrl: remoteOriginUrl,
|
||||
owner: parsedParentPath.name,
|
||||
repo: parsedPath.name
|
||||
};
|
||||
} else {
|
||||
window.showErrorMessage('Remote GitHub URL not found.');
|
||||
if (!suppressNotFoundErrors) {
|
||||
window.showErrorMessage('Remote GitHub URL not found.');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
window.showErrorMessage(`${workspaceFolder.name} does not have a Git repository`);
|
||||
if (!suppressNotFoundErrors) {
|
||||
window.showErrorMessage(`${workspaceFolder.name} does not have a Git repository`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
window.showErrorMessage('Git extension is still being initialized. Please try again later.', 'Try Again').then(async value => {
|
||||
if (value && value === 'Try Again') {
|
||||
await commands.executeCommand(command, ...args);
|
||||
const items = tryAgainOptions ? ['Try Again'] : [];
|
||||
window.showErrorMessage('Git extension is still being initialized. Please try again later.', ...items).then(async value => {
|
||||
if (value && value === 'Try Again' && tryAgainOptions) {
|
||||
await commands.executeCommand(tryAgainOptions.command, ...tryAgainOptions.args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
195
src/issueHandler.ts
Normal file
195
src/issueHandler.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
import * as childProcess from "child_process";
|
||||
import * as fs from "fs/promises";
|
||||
import * as path from "path";
|
||||
import { env, ExtensionContext, ProgressLocation, QuickPickItem, ThemeIcon, Uri, window, workspace } from "vscode";
|
||||
import { Act, Option } from "./act";
|
||||
import { act } from "./extension";
|
||||
|
||||
interface BugReport {
|
||||
githubLocalActionsVersion?: string
|
||||
actVersion?: string,
|
||||
githubRepositoryLink?: string,
|
||||
workflowContent?: string,
|
||||
actCommandUsed?: string,
|
||||
actCommandOutput?: string,
|
||||
actBugReport?: string
|
||||
bugDescription?: string,
|
||||
}
|
||||
|
||||
// Used to map bug report keys to ids and titles in the issue template
|
||||
const bugReportToTemplateMap: Record<keyof BugReport, { id: string, title: string }> = {
|
||||
githubLocalActionsVersion: { id: 'github-local-actions-version', title: 'Github Local Actions Version' },
|
||||
actVersion: { id: 'act-version', title: 'Act Version' },
|
||||
githubRepositoryLink: { id: 'github-repository-link', title: 'GitHub Repository Link' },
|
||||
workflowContent: { id: 'workflow-content', title: 'Workflow Content' },
|
||||
actCommandUsed: { id: 'act-command-used', title: 'Act Command Used' },
|
||||
actCommandOutput: { id: 'act-command-output', title: 'Act Command Output' },
|
||||
actBugReport: { id: 'act-bug-report', title: 'Act Bug Report' },
|
||||
bugDescription: { id: 'bug-description', title: 'Bug Description' }
|
||||
};
|
||||
|
||||
export namespace IssueHandler {
|
||||
export async function openBugReport(context: ExtensionContext) {
|
||||
try {
|
||||
const bugReport = await generateBugReport(context);
|
||||
if (bugReport) {
|
||||
const params = Object.entries(bugReport)
|
||||
.filter(([key, value]) => value !== undefined && value !== '')
|
||||
.map(([key, value]) => `${encodeURIComponent(bugReportToTemplateMap[key as keyof BugReport].id)}=${encodeURIComponent(value)}`)
|
||||
.join('&');
|
||||
|
||||
const bugReportUrl: string = 'https://github.com/SanjulaGanepola/github-local-actions/issues/new?assignees=&labels=bug&projects=&template=1-bug_report.yml';
|
||||
const urlWithParams = params ? `${bugReportUrl}&${params}` : bugReportUrl;
|
||||
await env.openExternal(Uri.parse(urlWithParams));
|
||||
}
|
||||
} catch (error) {
|
||||
await env.openExternal(Uri.parse('https://github.com/SanjulaGanepola/github-local-actions/issues'));
|
||||
}
|
||||
}
|
||||
|
||||
async function generateBugReport(context: ExtensionContext): Promise<BugReport | undefined> {
|
||||
return await window.withProgress({ location: ProgressLocation.Notification, title: 'Generating bug report...' }, async () => {
|
||||
const fullBugReport: BugReport = {};
|
||||
const infoItems: (QuickPickItem & { key: keyof BugReport })[] = [];
|
||||
|
||||
// Get extension version
|
||||
const githubLocalActionsVersion = context.extension.packageJSON.version;
|
||||
if (githubLocalActionsVersion) {
|
||||
fullBugReport.githubLocalActionsVersion = `v${githubLocalActionsVersion}`;
|
||||
infoItems.push({
|
||||
label: bugReportToTemplateMap['githubLocalActionsVersion'].title,
|
||||
description: fullBugReport.githubLocalActionsVersion,
|
||||
iconPath: new ThemeIcon('robot'),
|
||||
picked: true,
|
||||
key: 'githubLocalActionsVersion'
|
||||
});
|
||||
}
|
||||
|
||||
// Get act version
|
||||
const actVersion = (await act.componentsManager.getComponents()).find(component => component.name === 'nektos/act')?.version;
|
||||
if (actVersion) {
|
||||
fullBugReport.actVersion = `v${actVersion}`;
|
||||
infoItems.push({
|
||||
label: bugReportToTemplateMap['actVersion'].title,
|
||||
description: fullBugReport.actVersion,
|
||||
iconPath: new ThemeIcon('terminal'),
|
||||
picked: true,
|
||||
key: 'actVersion'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
let isWorkflowFound: boolean = false;
|
||||
const activeEditor = window.activeTextEditor;
|
||||
const workspaceFolder = activeEditor ?
|
||||
workspace.getWorkspaceFolder(activeEditor.document.uri) :
|
||||
(workspace.workspaceFolders && workspace.workspaceFolders.length > 0 ? workspace.workspaceFolders[0] : undefined);
|
||||
if (workspaceFolder) {
|
||||
// Get repository link
|
||||
const repository = await act.settingsManager.githubManager.getRepository(workspaceFolder, true);
|
||||
const githubRepositoryLink = repository?.remoteOriginUrl;
|
||||
if (githubRepositoryLink) {
|
||||
fullBugReport.githubRepositoryLink = githubRepositoryLink;
|
||||
infoItems.push({
|
||||
label: bugReportToTemplateMap['githubRepositoryLink'].title,
|
||||
description: fullBugReport.githubRepositoryLink,
|
||||
iconPath: new ThemeIcon('link'),
|
||||
picked: true,
|
||||
key: 'githubRepositoryLink'
|
||||
});
|
||||
}
|
||||
|
||||
if (activeEditor) {
|
||||
const workflows = await act.workflowsManager.getWorkflows(workspaceFolder);
|
||||
const workflow = workflows.find(workflow => workflow.uri.fsPath === activeEditor.document.uri.fsPath);
|
||||
if (workflow) {
|
||||
isWorkflowFound = true;
|
||||
|
||||
// Get workflow content
|
||||
const workflowContent = workflow?.fileContent;
|
||||
if (workflowContent) {
|
||||
fullBugReport.workflowContent = workflowContent;
|
||||
infoItems.push({
|
||||
label: bugReportToTemplateMap['workflowContent'].title,
|
||||
description: path.parse(workflow.uri.fsPath).base,
|
||||
iconPath: new ThemeIcon('file'),
|
||||
picked: true,
|
||||
key: 'workflowContent'
|
||||
});
|
||||
}
|
||||
|
||||
const workflowHistory = act.historyManager.workspaceHistory[workspaceFolder.uri.fsPath]?.filter(history => history.commandArgs.workflow?.uri.fsPath === workflow.uri.fsPath);
|
||||
if (workflowHistory && workflowHistory.length > 0) {
|
||||
// Get last act command
|
||||
const settings = await act.settingsManager.getSettings(workspaceFolder, true);
|
||||
const history = workflowHistory[workflowHistory.length - 1];
|
||||
const actCommandUsed = (await act.buildActCommand(settings, history.commandArgs.options)).displayCommand;
|
||||
if (actCommandUsed) {
|
||||
fullBugReport.actCommandUsed = actCommandUsed;
|
||||
infoItems.push({
|
||||
label: bugReportToTemplateMap['actCommandUsed'].title,
|
||||
description: `${history.name} #${history.count}`,
|
||||
iconPath: new ThemeIcon('code'),
|
||||
picked: true,
|
||||
key: 'actCommandUsed'
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// Get last act command output
|
||||
const actCommandOutput = await fs.readFile(history.logPath, 'utf8');
|
||||
if (actCommandOutput) {
|
||||
fullBugReport.actCommandOutput = actCommandOutput;
|
||||
infoItems.push({
|
||||
label: bugReportToTemplateMap['actCommandOutput'].title,
|
||||
description: path.parse(history.logPath).base,
|
||||
iconPath: new ThemeIcon('note'),
|
||||
picked: true,
|
||||
key: 'actCommandOutput'
|
||||
});
|
||||
}
|
||||
} catch (error: any) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get act bug report
|
||||
const actBugReport = await new Promise<string | undefined>((resolve, reject) => {
|
||||
childProcess.exec(`${Act.getActCommand()} ${Option.BugReport}`, (error, stdout, stderr) => {
|
||||
if (!error) {
|
||||
resolve(stdout);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (actBugReport) {
|
||||
fullBugReport.actBugReport = actBugReport;
|
||||
infoItems.push({
|
||||
label: bugReportToTemplateMap['actBugReport'].title,
|
||||
iconPath: new ThemeIcon('report'),
|
||||
picked: true,
|
||||
key: 'actBugReport'
|
||||
});
|
||||
}
|
||||
|
||||
const defaultTitle = 'Select the information to include in the bug report';
|
||||
const extendedTitle = 'More information can be included in the bug report by having the relevant workflow opened in the editor before invoking this command.';
|
||||
const selectedInfo = await window.showQuickPick(infoItems, {
|
||||
title: isWorkflowFound ? defaultTitle : `${defaultTitle}. ${extendedTitle}`,
|
||||
placeHolder: 'Bug Report Information',
|
||||
canPickMany: true
|
||||
});
|
||||
|
||||
if (selectedInfo) {
|
||||
const bugReport: BugReport = {};
|
||||
|
||||
const selectedInfoKeys = selectedInfo.map(info => info.key);
|
||||
for (const key of selectedInfoKeys) {
|
||||
bugReport[key] = fullBugReport[key];
|
||||
}
|
||||
|
||||
return bugReport;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -289,7 +289,7 @@ export default class SettingsTreeDataProvider implements TreeDataProvider<Github
|
||||
const settings = await act.settingsManager.getSettings(settingTreeItem.workspaceFolder, false);
|
||||
const variableNames = settings.variables.map(variable => variable.key);
|
||||
if (variableNames.length > 0) {
|
||||
const repository = await act.settingsManager.githubManager.getRepository(settingTreeItem.workspaceFolder, 'githubLocalActions.importFromGithub', [settingTreeItem]);
|
||||
const repository = await act.settingsManager.githubManager.getRepository(settingTreeItem.workspaceFolder, false, { command: 'githubLocalActions.importFromGithub', args: [settingTreeItem] });
|
||||
if (repository) {
|
||||
const variableOptions: QuickPickItem[] = [];
|
||||
const errors: string[] = [];
|
||||
|
||||
Reference in New Issue
Block a user