Skip to content

Commit

Permalink
implemented a better hierarchy in builds view, added approve and decl…
Browse files Browse the repository at this point in the history
…ine buttons, improved status icons
  • Loading branch information
maximtrp committed Mar 7, 2023
1 parent 10d6285 commit 9fc6f41
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 73 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Change Log

## 1.2.0 - 2023-03-08

- Builds view has a build → stage → step hierarchy now.
- Added Approve and Decline buttons to stage items in Builds view.
- Improved status icons in Builds view.

## 1.1.0 - 2023-03-07

- Improved error handling.
Expand All @@ -12,4 +18,4 @@

## 1.0.0 - 2023-03-04

- Initial release
- Initial release.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/maximtrp/droneci-vscode-extension/main.yml)
![Visual Studio Marketplace Version](https://img.shields.io/visual-studio-marketplace/v/maximtrp.drone-ci)
![Codacy Grade](https://img.shields.io/codacy/grade/5bfec3730914417d958e8dfb3bd00f3e/main)

This extension integrates Drone CI into VS Code. It allows you to quickly browse and manage your repos, builds, secrets
and cron jobs.
Expand All @@ -17,7 +18,7 @@ and cron jobs.

## Installation

This extension is available on the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=maximtrp.drone-ci) for Visual Studio Code.
This extension is available on the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=maximtrp.drone-ci) for Visual Studio Code and [Open VSX Registry](https://open-vsx.org/extension/maximtrp/drone-ci).

## Copyright

Expand Down
38 changes: 14 additions & 24 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "drone-ci",
"displayName": "Drone CI",
"description": "Manage your Drone CI servers easily",
"version": "1.1.0",
"version": "1.2.0",
"publisher": "maximtrp",
"author": {
"name": "Maksim Terpilovskii",
Expand Down Expand Up @@ -176,12 +176,12 @@
"icon": "$(open-preview)"
},
{
"command": "drone-ci.approveBuild",
"command": "drone-ci.approveBuildStage",
"title": "Approve",
"icon": "$(check)"
},
{
"command": "drone-ci.declineBuild",
"command": "drone-ci.declineBuildStage",
"title": "Decline",
"icon": "$(close)"
},
Expand Down Expand Up @@ -389,19 +389,19 @@
"when": "view == drone-ci-repos && viewItem =~ /^repo/"
},
{
"command": "drone-ci.approveBuild",
"when": "view == drone-ci-builds && viewItem == build_blocked",
"group": "actions"
"command": "drone-ci.approveBuildStage",
"when": "view == drone-ci-builds && viewItem == stage_blocked",
"group": "inline"
},
{
"command": "drone-ci.declineBuild",
"when": "view == drone-ci-builds && viewItem == build_blocked",
"group": "actions"
"command": "drone-ci.declineBuildStage",
"when": "view == drone-ci-builds && viewItem == stage_blocked",
"group": "inline"
},
{
"command": "drone-ci.cancelBuild",
"when": "view == drone-ci-builds && viewItem == build_running",
"group": "actions"
"group": "inline"
},
{
"command": "drone-ci.restartBuild",
Expand Down Expand Up @@ -516,16 +516,6 @@
}
],
"drone-ci.build": [
{
"command": "drone-ci.approveBuild",
"when": "view == drone-ci-builds && viewItem == build_blocked",
"group": "actions"
},
{
"command": "drone-ci.declineBuild",
"when": "view == drone-ci-builds && viewItem == build_blocked",
"group": "actions"
},
{
"command": "drone-ci.restartBuild",
"when": "view == drone-ci-builds && viewItem =~ /^build/",
Expand Down Expand Up @@ -645,19 +635,19 @@
"when": "false"
},
{
"command": "drone-ci.viewBuildStepLog",
"command": "drone-ci.declineBuildStage",
"when": "false"
},
{
"command": "drone-ci.viewBuildInfo",
"command": "drone-ci.approveBuildStage",
"when": "false"
},
{
"command": "drone-ci.approveBuild",
"command": "drone-ci.viewBuildStepLog",
"when": "false"
},
{
"command": "drone-ci.declineBuild",
"command": "drone-ci.viewBuildInfo",
"when": "false"
},
{
Expand Down
147 changes: 100 additions & 47 deletions src/builds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ interface BuildInfo {
}

interface StageInfo {
name: string;
number: number;
status: string;
steps: StepInfo[];
}

Expand All @@ -46,6 +48,33 @@ interface BuildIcon {
color: string;
}

function getIcon(status: string = "fallback", event?: string): vscode.ThemeIcon {
let presets: { [status: string]: BuildIcon } = {
skipped: { icon: "debug-step-over", color: "disabledForeground" },
declined: { icon: "circle-slash", color: "charts.red" },
// eslint-disable-next-line @typescript-eslint/naming-convention
waiting_on_dependencies: { icon: "watch", color: "charts.yellow" },
blocked: { icon: "warning", color: "charts.yellow" },
success: { icon: "pass", color: "charts.green" },
error: { icon: "error", color: "charts.red" },
failure: { icon: "error", color: "charts.red" },
pending: { icon: "sync~spin", color: "charts.blue" },
killed: { icon: "stop-circle", color: "charts.red" },
running: { icon: "run", color: "charts.green" },
fallback: { icon: "pass", color: "charts.purple" },
};
let selectedPreset: BuildIcon = presets[status] || presets.fallback;
if (event === "custom" && status !== "running") {
selectedPreset.icon = "repl";
} else if (event === "promote" && status !== "running") {
selectedPreset.icon = "rocket";
} else if (event === "cron" && status !== "running") {
selectedPreset.icon = "calendar";
}

return new vscode.ThemeIcon(selectedPreset.icon, new vscode.ThemeColor(selectedPreset.color));
}

export class BuildsProvider implements vscode.TreeDataProvider<Build | Step | None> {
private client: any | null = null;
public data: RepoInfo | null = null;
Expand Down Expand Up @@ -75,25 +104,30 @@ export class BuildsProvider implements vscode.TreeDataProvider<Build | Step | No
return element;
}

async getChildren(build?: Build): Promise<Build[] | Step[] | LoadMore[] | None[]> {
async getChildren(element?: Build | Stage): Promise<Build[] | Step[] | LoadMore[] | None[]> {
if (this.client && this.data) {
if (build) {
let buildInfo: BuildInfo | null = await this.client.getBuild(this.data.owner, this.data.repo, build.number);

if (buildInfo && buildInfo.stages && buildInfo.stages[0].steps) {
const stepsInfo: { step: StepInfo; stage: StageInfo; build: number }[] = buildInfo.stages
.map((stage) =>
stage.steps.map((step) => ({
build: build.number,
stage,
step,
}))
)
.flat();
let steps: Step[] = stepsInfo.map((stepInfo) => new Step(stepInfo.step, stepInfo.stage, stepInfo.build));
if (steps.length > 0) {
return steps;
if (element) {
if (element.contextValue?.startsWith("build")) {
try {
let buildInfo: BuildInfo | null = await this.client.getBuild(
this.data.owner,
this.data.repo,
element.number
);
if (buildInfo && buildInfo.stages && buildInfo.stages.length > 0) {
return buildInfo.stages.map((stage) => new Stage(stage, buildInfo!.number));
} else {
return [new None("No stages found")];
}
} catch (e) {
return [new None("Error occurred while loading build stages")];
}
} else if (element.contextValue?.startsWith("stage")) {
const stage = element as Stage;
if (stage.steps && stage.steps.length > 0) {
return stage.steps.map((step) => new Step(step, stage.number, stage.build));
}
return [new None("No steps found")];
}
return [new None("Nothing found")];
} else {
Expand Down Expand Up @@ -208,6 +242,32 @@ export class BuildsProvider implements vscode.TreeDataProvider<Build | Step | No
}
}

async approveBuildStage(stage: Stage) {
if (this.data) {
try {
await this.client.approveBuild(this.data.owner, this.data.repo, stage.build, stage.number);
this.refresh();
} catch (error: any) {
vscode.window.showWarningMessage(
`Stage ${stage.number} of build ${stage.build} was not approved. ${error.message}`
);
}
}
}

async declineBuildStage(stage: Stage) {
if (this.data) {
try {
await this.client.declineBuild(this.data.owner, this.data.repo, stage.build, stage.number);
this.refresh();
} catch (error: any) {
vscode.window.showWarningMessage(
`Stage ${stage.number} of build ${stage.build} was not declined. ${error.message}`
);
}
}
}

async restartBuild(build: Build) {
if (this.data) {
try {
Expand Down Expand Up @@ -303,20 +363,35 @@ class LoadMore extends vscode.TreeItem {
}
}

class Stage extends vscode.TreeItem {
public number: number;
public build: number;
public steps?: StepInfo[];

constructor(stage: StageInfo, build: number) {
const label = `Stage ${stage.number}: ${stage.name}`;
super(label, vscode.TreeItemCollapsibleState.Collapsed);
this.contextValue = stage.status === "blocked" ? "stage_blocked" : "stage";
this.number = stage.number;
this.steps = stage.steps;
this.build = build;
this.iconPath = getIcon(stage.status);
}
}

class Step extends vscode.TreeItem {
public step: number;
public stage: number;
public build: number;

constructor(step: StepInfo, stage: StageInfo, public build: number) {
const label = `Stage ${stage.number}: ${step.name}`;
constructor(step: StepInfo, stage: number, build: number) {
const label = `Step ${step.number}: ${step.name}`;
super(label, vscode.TreeItemCollapsibleState.None);
this.contextValue = "step";
this.step = step.number;
this.stage = stage.number;
this.iconPath =
step.status === "success"
? new vscode.ThemeIcon("pass", new vscode.ThemeColor("charts.green"))
: new vscode.ThemeIcon("error", new vscode.ThemeColor("charts.red"));
this.stage = stage;
this.build = build;
this.iconPath = getIcon(step.status);
}
}

Expand Down Expand Up @@ -344,28 +419,6 @@ class Build extends vscode.TreeItem {
.filter((i) => !!i)
.join("\n");
this.contextValue = `build_${build.status}`;
this.iconPath = this.selectIcon(build);
}

selectIcon(build: BuildInfo): vscode.ThemeIcon {
let presets: { [status: string]: BuildIcon } = {
success: { icon: "pass", color: "charts.green" },
error: { icon: "error", color: "charts.red" },
failure: { icon: "error", color: "charts.red" },
pending: { icon: "sync~spin", color: "charts.blue" },
killed: { icon: "stop-circle", color: "charts.red" },
running: { icon: "run", color: "charts.green" },
fallback: { icon: "pass", color: "charts.yellow" },
};
let selectedPreset: BuildIcon = presets[build.status] || presets.fallback;
if (build.event === "custom" && build.status !== "running") {
selectedPreset.icon = "repl";
} else if (build.event === "promote" && build.status !== "running") {
selectedPreset.icon = "rocket";
} else if (build.event === "cron" && build.status !== "running") {
selectedPreset.icon = "calendar";
}

return new vscode.ThemeIcon(selectedPreset.icon, new vscode.ThemeColor(selectedPreset.color));
this.iconPath = getIcon(build.status, build.event);
}
}
8 changes: 8 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ export function activate(context: vscode.ExtensionContext) {
let commandPromoteBuild = vscode.commands.registerCommand("drone-ci.promoteBuild", (build) =>
buildsProvider.promoteBuild(build)
);
let commandApproveBuildStage = vscode.commands.registerCommand("drone-ci.approveBuildStage", (stage) =>
buildsProvider.approveBuildStage(stage)
);
let commandDeclineBuildStage = vscode.commands.registerCommand("drone-ci.declineBuildStage", (stage) =>
buildsProvider.declineBuildStage(stage)
);
let commandCancelBuild = vscode.commands.registerCommand("drone-ci.cancelBuild", (build) =>
buildsProvider.cancelBuild(build)
);
Expand Down Expand Up @@ -196,6 +202,8 @@ export function activate(context: vscode.ExtensionContext) {
commandAddCron,
commandEditCron,
commandTriggerBuild,
commandApproveBuildStage,
commandDeclineBuildStage,
commandDisableRepo,
commandEnableRepo,
commandRepairRepo,
Expand Down

0 comments on commit 9fc6f41

Please sign in to comment.