Skip to content

Commit

Permalink
Merge pull request #16 from heroku/jw/resource-explorer-addons
Browse files Browse the repository at this point in the history
Displayed addons in tree view. Added polling for dyno restarts.
  • Loading branch information
justinwilaby authored Aug 12, 2024
2 parents d1b9cd4 + 63a617d commit 743577b
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 13 deletions.
24 changes: 20 additions & 4 deletions src/extension/commands/dyno/restart-dyno.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export class RestartDynoCommand extends AbortController implements RunnableComma
* @returns void
*/
public async run(dyno: Dyno): Promise<void> {
const confirmation = await vscode.window.showWarningMessage(`This action will restart the ${dyno.name} dyno`, {modal: true, detail:'This action may interupt traffic to your Dyno'}, 'Cancel', 'Restart');
const confirmation = await vscode.window.showWarningMessage(`This action will restart the ${dyno.name} dyno`, {modal: true, detail:'This action may interupt traffic to your Dyno'}, 'Restart');
if (confirmation !== 'Restart') {
return;
}
Expand All @@ -29,12 +29,28 @@ export class RestartDynoCommand extends AbortController implements RunnableComma
const requestInit = { signal: this.signal, headers: { Authorization: `Bearer ${accessToken}` } };

try {
const { state } = await this.dynoService.info(dyno.app.id as string, dyno.id, requestInit);
let state: string;
let id: string;
({ state } = await this.dynoService.info(dyno.app.id as string, dyno.id, requestInit));
if (state !== 'starting') {
Reflect.set(dyno, 'state', 'restarting');
vscode.window.setStatusBarMessage(`${dyno.name} is restarting...`);
await this.dynoService.restart(dyno.app.id as string, dyno.id, requestInit);
}
vscode.window.setStatusBarMessage(`${dyno.name} is restarting...`);
} catch {

let retries = 120;
while (!this.signal.aborted) {
({ state, id } = await this.dynoService.info(dyno.app.id as string, dyno.name, requestInit));
vscode.window.setStatusBarMessage(`${dyno.name} is ${state}`);
Reflect.set(dyno, 'state', state);
Reflect.set(dyno, 'id', id);
retries--;
if (!retries || dyno.state === 'up' || dyno.state === 'crashed') {
break;
}
await new Promise(resolve => setTimeout(resolve, 2000));
}
} catch(e) {
await vscode.window.showErrorMessage(`Could not restart ${dyno.name}.`);
}
}
Expand Down
10 changes: 7 additions & 3 deletions src/extension/commands/dyno/restart.dyno.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,17 @@ suite('The RestartDynoCommand', () => {
});

test('restarts the dyno', async () => {
fetchStub.callsFake(async () => {
return new Response('{}');
fetchStub.onFirstCall().callsFake(async () => {
return new Response(JSON.stringify({id: '1234', state: 'starting'} as Dyno));
});

fetchStub.onSecondCall().callsFake(async () => {
return new Response(JSON.stringify({ id: '1234', state: 'up' } as Dyno));
});

await vscode.commands.executeCommand<string>(RestartDynoCommand.COMMAND_ID, dyno);
assert.ok(!!getSessionStub.exceptions.length);
assert.ok(setStatusBarMessageStub.calledWith(`${dyno.name} is restarting...`));
assert.ok(setStatusBarMessageStub.calledWith('tester-dyno is up'));
});

test('shows appropriate status message when restarting fails', async () => {
Expand Down
2 changes: 1 addition & 1 deletion src/extension/commands/dyno/stop-dyno.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class StopDynoCommand extends AbortController implements RunnableCommand<
* @returns void
*/
public async run(dyno: Dyno): Promise<void> {
const confirmation = await vscode.window.showWarningMessage(`This action will scae the ${dyno.name} dyno to zero.`, {modal: true}, 'Cancel', 'Stop Dyno');
const confirmation = await vscode.window.showWarningMessage(`This action will scale the ${dyno.name} dyno to zero.`, {modal: true}, 'Stop Dyno');
if (confirmation !== 'Stop Dyno') {
return;
}
Expand Down
54 changes: 49 additions & 5 deletions src/extension/providers/heroku-resource-explorer-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export class HerokuResourceExplorerProvider<T extends (App | Bindable<Dyno> | Bi
/**
* @inheritdoc
*/
public getTreeItem(element: T): vscode.TreeItem {
public async getTreeItem(element: T): Promise<vscode.TreeItem> {
switch(this.elementTypeMap.get(element)) {
case 'AddOn':
return this.getAddOnTreeItem(element as AddOn);
Expand Down Expand Up @@ -89,6 +89,9 @@ export class HerokuResourceExplorerProvider<T extends (App | Bindable<Dyno> | Bi
case 'ADDONS':
return await this.getAddonsForApp(appIdentifier) as T[];

case 'SETTINGS':
return this.getSettingsCategories(appIdentifier) as T[];

default:
return [];
}
Expand Down Expand Up @@ -172,7 +175,7 @@ export class HerokuResourceExplorerProvider<T extends (App | Bindable<Dyno> | Bi
/**
* Gets the main tree nodes for the resource explorer.
*
* @param appIdentifier The app
* @param appIdentifier The app id or name
* @returns an array of TreeItem
*/
private getAppCategories(appIdentifier: string): vscode.TreeItem[] {
Expand All @@ -195,6 +198,37 @@ export class HerokuResourceExplorerProvider<T extends (App | Bindable<Dyno> | Bi
];
}

/**
* Gets the categories for the settings section.
*
* @param appIdentifier The app id or name
* @returns vscode.TreeItem[]
*/
private getSettingsCategories(appIdentifier: string): vscode.TreeItem[] {
return [
{
id: appIdentifier + ':app-info',
label: 'App Information',
},
{
id: appIdentifier + ':config-vars',
label: 'Config Vars',
},
{
id: appIdentifier + ':buildpacks',
label: 'Buildpacks',
},
{
id: appIdentifier + ':ssl-certs',
label: 'SSL Certificates',
},
{
id: appIdentifier + ':domains',
label: 'Domains',
},
];
}

/**
* Consumes a Dyno object and returns a TreeItem.
*
Expand Down Expand Up @@ -234,12 +268,22 @@ export class HerokuResourceExplorerProvider<T extends (App | Bindable<Dyno> | Bi
* @param addOn The AddOn to convert to a TreeItem
* @returns The TreeItem from the specified Dyno
*/
private getAddOnTreeItem(addOn: AddOn): vscode.TreeItem {
private async getAddOnTreeItem(addOn: AddOn): Promise<vscode.TreeItem> {
// Grab the icon from https://addons.heroku.com
let iconUrl: string | undefined;
try {
const addonsApiResponse = await fetch(`https://addons.heroku.com/api/v2/addons/${addOn.addon_service.id}`);
const json = await addonsApiResponse.json() as {addon:{icon_url: string}};
iconUrl = json.addon.icon_url;
} catch {
// no-op - don't worry, this won't break things too badly.
}
return {
id: addOn.id,
label: addOn.name,
description: addOn.plan.name,
label: addOn.addon_service.name,
description: `(${addOn.name})`,
tooltip: `${addOn.app.name} - ${addOn.state}`,
iconPath: iconUrl ? vscode.Uri.parse(`https://addons.heroku.com/${iconUrl}`) : undefined
} as vscode.TreeItem;
}
}

0 comments on commit 743577b

Please sign in to comment.