Init log tracking

Signed-off-by: Sanjula Ganepola <sanjulagane@gmail.com>
This commit is contained in:
Sanjula Ganepola
2024-10-01 23:06:48 -04:00
parent 41a19ab041
commit 986e958a9c
8 changed files with 210 additions and 55 deletions

View File

@@ -2,6 +2,7 @@ import * as child_process from 'child_process';
import * as path from "path";
import { commands, CustomExecution, env, EventEmitter, Pseudoterminal, ShellExecution, TaskDefinition, TaskGroup, TaskPanelKind, TaskRevealKind, tasks, TaskScope, TerminalDimensions, window, workspace } from "vscode";
import { ComponentsManager } from "./componentsManager";
import { workflowsTreeDataProvider } from './extension';
import { SettingsManager } from './settingsManager';
import { Workflow, WorkflowsManager } from "./workflowsManager";
@@ -42,12 +43,73 @@ export enum EventTrigger {
}
export enum Option {
Workflows = '-W',
Variable = '-var'
Workflows = '--workflows',
Variable = '--var',
Json = "--json"
}
export interface WorkflowLog {
name: string,
status: WorkflowStatus
jobLogs: JobLog[]
}
export interface JobLog {
name: string,
status: JobStatus,
stepLogs: StepLog[]
}
export interface StepLog {
name: string,
status: StepStatus
}
export interface RawLog {
dryrun: boolean,
job: string,
jobID: string,
level: string, //TODO: Could be an enum?
matrix: any,
msg: string,
time: string,
stage: string,
step: string,
stepID: string[],
jobResult: string, //TODO: Could be an enum?
}
export enum WorkflowStatus {
Queued = 'queued',
InProgress = 'inProgress',
Success = 'success',
Failed = 'failed',
Cancelled = 'cancelled'
}
export enum JobStatus {
Queued = 'queued',
InProgress = 'inProgress',
Skipped = 'skipped',
Success = 'success',
Failed = 'failed',
Cancelled = 'cancelled'
}
export enum StepStatus {
Queued = 'queued',
InProgress = 'inProgress',
Skipped = 'skipped',
Success = 'success',
Failed = 'failed',
Cancelled = 'cancelled'
}
export class Act {
private static base: string = 'act';
workflowLogs: { [path: string]: WorkflowLog[] };
componentsManager: ComponentsManager;
workflowsManager: WorkflowsManager;
settingsManager: SettingsManager;
@@ -55,6 +117,7 @@ export class Act {
prebuiltExecutables: { [architecture: string]: string };
constructor() {
this.workflowLogs = {};
this.componentsManager = new ComponentsManager();
this.workflowsManager = new WorkflowsManager();
this.settingsManager = new SettingsManager();
@@ -120,7 +183,7 @@ export class Act {
}
async runWorkflow(workflow: Workflow) {
return await this.runCommand(workflow, `${Act.base} ${Option.Workflows} ".github/workflows/${path.parse(workflow.uri.fsPath).base}"`);
return await this.runCommand(workflow, `${Act.base} ${Option.Json} ${Option.Workflows} ".github/workflows/${path.parse(workflow.uri.fsPath).base}"`);
}
async runCommand(workflow: Workflow, command: string) {
@@ -140,6 +203,29 @@ export class Act {
return;
}
if (!this.workflowLogs[workflow.uri.fsPath]) {
this.workflowLogs[workflow.uri.fsPath] = [];
}
this.workflowLogs[workflow.uri.fsPath].push({
name: `${workflow.name} #${this.workflowLogs[workflow.uri.fsPath].length + 1}`,
status: WorkflowStatus.Queued,
jobLogs: Object.entries<any>(workflow.yaml.jobs).map(([key, value]) => {
return {
name: value.name ? value.name : key,
status: JobStatus.Queued,
stepLogs: Object.entries<any>(workflow.yaml.jobs[key].steps).map(([key, value]) => {
return {
name: value.name ? value.name : key,
status: StepStatus.Queued,
stepLogs: []
}
})
}
})
});
workflowsTreeDataProvider.refresh();
await tasks.executeTask({
name: workflow.name,
detail: 'Run workflow',
@@ -178,8 +264,25 @@ export class Act {
const exec = child_process.spawn(command, { cwd: workspaceFolder.uri.fsPath, shell: env.shell });
exec.stdout.on('data', (data) => {
const output = data.toString().replaceAll('\n', '\r\n');
writeEmitter.fire(output);
const lines = data.toString().split('\n');
for (const line of lines) {
const rawLog: RawLog = JSON.parse(line);
if (rawLog.stepID) {
} else if (rawLog.jobID) {
// this.workflowLogs[workflow.uri.fsPath][Object.values(this.workflowLogs).length - 1].jobLogs.push({
// name: '',
// status: '',
// stepLogs: []
// });
} else if (rawLog.jobResult) {
} else {
}
}
writeEmitter.fire(lines);
});
exec.stderr.on('data', (data) => {

View File

@@ -1,6 +1,6 @@
import * as child_process from "child_process";
import { commands, env, extensions, QuickPickItemKind, ShellExecution, TaskGroup, TaskPanelKind, TaskRevealKind, tasks, TaskScope, ThemeIcon, Uri, window } from "vscode";
import { act } from "./extension";
import { env, extensions, QuickPickItemKind, ShellExecution, TaskGroup, TaskPanelKind, TaskRevealKind, tasks, TaskScope, ThemeIcon, Uri, window } from "vscode";
import { act, componentsTreeDataProvider } from "./extension";
export interface Component<T extends CliStatus | ExtensionStatus> {
name: string,
@@ -102,7 +102,7 @@ export class ComponentsManager {
await env.openExternal(Uri.parse(selectedPrebuiltExecutable.link));
window.showInformationMessage('Unpack and run the executable in the terminal specifying the full path or add it to one of the paths in your PATH environment variable. Once nektos/act is successfully installed, refresh the components view.', 'Refresh').then(async value => {
if (value === 'Refresh') {
await commands.executeCommand('githubLocalActions.refreshComponents');
componentsTreeDataProvider.refresh();
}
});
}
@@ -110,7 +110,7 @@ export class ComponentsManager {
await env.openExternal(Uri.parse(selectedInstallationMethod.link));
window.showInformationMessage('Once nektos/act is successfully installed, refresh the components view.', 'Refresh').then(async value => {
if (value === 'Refresh') {
await commands.executeCommand('githubLocalActions.refreshComponents');
componentsTreeDataProvider.refresh();
}
});
} else {
@@ -170,7 +170,7 @@ export class ComponentsManager {
window.showInformationMessage('Once Docker Engine is successfully started, refresh the components view.', 'Refresh').then(async value => {
if (value === 'Refresh') {
await commands.executeCommand('githubLocalActions.refreshComponents');
componentsTreeDataProvider.refresh();
}
});
}

View File

@@ -7,6 +7,9 @@ import SettingsTreeDataProvider from './views/settings/settingsTreeDataProvider'
import WorkflowsTreeDataProvider from './views/workflows/workflowsTreeDataProvider';
export let act: Act;
export let componentsTreeDataProvider: ComponentsTreeDataProvider;
export let workflowsTreeDataProvider: WorkflowsTreeDataProvider;
export let settingsTreeDataProvider: SettingsTreeDataProvider;
export function activate(context: vscode.ExtensionContext) {
console.log('Congratulations, your extension "github-local-actions" is now active!');
@@ -14,11 +17,11 @@ export function activate(context: vscode.ExtensionContext) {
act = new Act();
const decorationProvider = new DecorationProvider();
const componentsTreeDataProvider = new ComponentsTreeDataProvider(context);
componentsTreeDataProvider = new ComponentsTreeDataProvider(context);
const componentsTreeView = window.createTreeView(ComponentsTreeDataProvider.VIEW_ID, { treeDataProvider: componentsTreeDataProvider });
const workflowsTreeDataProvider = new WorkflowsTreeDataProvider(context);
workflowsTreeDataProvider = new WorkflowsTreeDataProvider(context);
const workflowsTreeView = window.createTreeView(WorkflowsTreeDataProvider.VIEW_ID, { treeDataProvider: workflowsTreeDataProvider });
const settingsTreeDataProvider = new SettingsTreeDataProvider(context);
settingsTreeDataProvider = new SettingsTreeDataProvider(context);
const settingsTreeView = window.createTreeView(SettingsTreeDataProvider.VIEW_ID, { treeDataProvider: settingsTreeDataProvider });
context.subscriptions.push(
componentsTreeView,

View File

@@ -0,0 +1,30 @@
import { ThemeIcon, TreeItem, TreeItemCollapsibleState } from "vscode";
import { JobLog } from "../../act";
import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem";
import StepTreeItem from "./stepLog";
export default class JobLogTreeItem extends TreeItem implements GithubLocalActionsTreeItem {
static contextValue = 'githubLocalActions.jobLog';
jobLog: JobLog;
constructor(jobLog: JobLog) {
super(jobLog.name, TreeItemCollapsibleState.Collapsed);
this.jobLog = jobLog;
this.contextValue = JobLogTreeItem.contextValue;
this.iconPath = new ThemeIcon('pass-filled');
// this.tooltip = `Name: ${workflow.name}\n` +
// `Path: ${workflow.uri.fsPath}\n` +
// (workflow.error ? `Error: ${workflow.error}` : ``);
// TODO: Add tooltip and resourceUri
}
async getChildren(): Promise<GithubLocalActionsTreeItem[]> {
const stepLogs = this.jobLog.stepLogs;
if (stepLogs) {
return this.jobLog.stepLogs.map(step => new StepTreeItem(step));
} else {
return [];
}
}
}

View File

@@ -0,0 +1,24 @@
import { ThemeIcon, TreeItem, TreeItemCollapsibleState } from "vscode";
import { StepLog } from "../../act";
import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem";
export default class StepLogTreeItem extends TreeItem implements GithubLocalActionsTreeItem {
static contextValue = 'githubLocalActions.stepLog';
stepLog: StepLog;
constructor(stepLog: StepLog) {
super(stepLog.name, TreeItemCollapsibleState.None);
this.stepLog = stepLog;
this.contextValue = StepLogTreeItem.contextValue;
this.iconPath = new ThemeIcon('pass-filled');
// this.tooltip = `Name: ${workflow.name}\n` +
// `Path: ${workflow.uri.fsPath}\n` +
// (workflow.error ? `Error: ${workflow.error}` : ``);
// TODO: Add tooltip and resourceUri
}
async getChildren(): Promise<GithubLocalActionsTreeItem[]> {
return [];
}
}

View File

@@ -1,6 +1,8 @@
import { ThemeIcon, TreeItem, TreeItemCollapsibleState, Uri } from "vscode";
import { act } from "../../extension";
import { Workflow } from "../../workflowsManager";
import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem";
import WorkflowLogTreeItem from "./workflowLog";
export default class WorkflowTreeItem extends TreeItem implements GithubLocalActionsTreeItem {
static contextValue = 'githubLocalActions.workflow';
@@ -21,6 +23,11 @@ export default class WorkflowTreeItem extends TreeItem implements GithubLocalAct
}
async getChildren(): Promise<GithubLocalActionsTreeItem[]> {
const workflowLogs = act.workflowLogs[this.workflow.uri.fsPath];
if (workflowLogs) {
return workflowLogs.map(workflowLog => new WorkflowLogTreeItem(workflowLog));
} else {
return [];
}
}
}

View File

@@ -0,0 +1,25 @@
import { ThemeIcon, TreeItem, TreeItemCollapsibleState } from "vscode";
import { WorkflowLog } from "../../act";
import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem";
import JobLogTreeItem from "./jobLog";
export default class WorkflowLogTreeItem extends TreeItem implements GithubLocalActionsTreeItem {
static contextValue = 'githubLocalActions.workflowLog';
workflowLog: WorkflowLog;
constructor(workflowLog: WorkflowLog) {
super(workflowLog.name, TreeItemCollapsibleState.Collapsed);
this.workflowLog = workflowLog;
this.contextValue = WorkflowLogTreeItem.contextValue;
this.iconPath = new ThemeIcon('pass-filled');
// this.tooltip = `Name: ${workflow.name}\n` +
// `Path: ${workflow.uri.fsPath}\n` +
// (workflow.error ? `Error: ${workflow.error}` : ``);
// TODO: Add tooltip and resourceUri
}
async getChildren(): Promise<GithubLocalActionsTreeItem[]> {
return this.workflowLog.jobLogs.map(jobLog => new JobLogTreeItem(jobLog));
}
}

View File

@@ -11,44 +11,7 @@ export interface Workflow {
error?: string
}
export interface WorkflowLog {
workflow: Workflow,
status: WorkflowStatus
}
export enum WorkflowStatus {
Queued = 'queued',
InProgress = 'inProgress',
success = 'success',
failed = 'failed',
Cancelled = 'cancelled'
}
export enum JobStatus {
Queued = 'queued',
InProgress = 'inProgress',
Skipped = 'skipped',
success = 'success',
failed = 'failed',
Cancelled = 'cancelled'
}
export enum StepStatus {
Queued = 'queued',
InProgress = 'inProgress',
Skipped = 'skipped',
success = 'success',
failed = 'failed',
Cancelled = 'cancelled'
}
export class WorkflowsManager {
private workflowLogs: WorkflowLog[] = [];
constructor() {
}
async getWorkflows(): Promise<Workflow[]> {
const workflows: Workflow[] = [];