Skip to content

Commit

Permalink
Add TFC workspace pagination (#1587)
Browse files Browse the repository at this point in the history
* Only refresh runs for workspace items

* Introduce cache and loadmore item

* Clear cache on refresh

* Enable loading indicator on load more

* Fix error on org change

* Reset cache AND workspace count

* Use next-page instead of total-count
  • Loading branch information
dbanck authored and jpogran committed Nov 6, 2023
1 parent a653b74 commit 8ef19f4
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 17 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@
},
{
"command": "terraform.cloud.workspaces.viewInBrowser",
"when": "view == terraform.cloud.workspaces",
"when": "view == terraform.cloud.workspaces && viewItem =~ /hasLink/",
"group": "inline"
},
{
Expand Down
15 changes: 7 additions & 8 deletions src/features/terraformCloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,19 @@ export class TerraformCloudFeature implements vscode.Disposable {
}

// we don't allow multi-select yet so this will always be one
const workspaceItem = event.selection[0] as WorkspaceTreeItem;

// call the TFC Run provider with the workspace
runDataProvider.refresh(workspaceItem);
const item = event.selection[0];
if (item instanceof WorkspaceTreeItem) {
// call the TFC Run provider with the workspace
runDataProvider.refresh(item);
}
});

// TODO: move this as the login/organization picker is fleshed out
// where it can handle things better
vscode.authentication.onDidChangeSessions((e) => {
// Refresh the workspace list if the user changes session
if (e.provider.id === TerraformCloudAuthenticationProvider.providerID) {
workspaceDataProvider.reset();
workspaceDataProvider.refresh();
runDataProvider.refresh();
}
Expand Down Expand Up @@ -154,10 +156,7 @@ export class TerraformCloudFeature implements vscode.Disposable {

// project filter should be cleared on org change
await vscode.commands.executeCommand('terraform.cloud.workspaces.resetProjectFilter');

// refresh workspaces so they pick up the change
workspaceDataProvider.refresh();
runDataProvider.refresh();
// filter reset will refresh workspaces
}),
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/providers/tfc/runProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class RunTreeDataProvider implements vscode.TreeDataProvider<TFCRunTreeIt
}

getChildren(element?: TFCRunTreeItem | undefined): vscode.ProviderResult<TFCRunTreeItem[]> {
if (!this.activeWorkspace) {
if (!this.activeWorkspace || !(this.activeWorkspace instanceof WorkspaceTreeItem)) {
return [];
}

Expand Down
57 changes: 50 additions & 7 deletions src/providers/tfc/workspaceProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ import { isErrorFromAlias, ZodiosError } from '@zodios/core';
import axios from 'axios';
import { apiErrorsToString } from '../../terraformCloud/errors';

export class WorkspaceTreeDataProvider implements vscode.TreeDataProvider<WorkspaceTreeItem>, vscode.Disposable {
private readonly didChangeTreeData = new vscode.EventEmitter<void | WorkspaceTreeItem>();
export class WorkspaceTreeDataProvider implements vscode.TreeDataProvider<vscode.TreeItem>, vscode.Disposable {
private readonly didChangeTreeData = new vscode.EventEmitter<void | vscode.TreeItem>();
public readonly onDidChangeTreeData = this.didChangeTreeData.event;
private projectFilter: string | undefined;
private pageSize = 50;
private cache: vscode.TreeItem[] = [];
private nextPage: number | null = null;

constructor(
private ctx: vscode.ExtensionContext,
Expand All @@ -32,12 +35,14 @@ export class WorkspaceTreeDataProvider implements vscode.TreeDataProvider<Worksp
this.ctx.subscriptions.push(
vscode.commands.registerCommand('terraform.cloud.workspaces.refresh', (workspaceItem: WorkspaceTreeItem) => {
this.reporter.sendTelemetryEvent('tfc-workspaces-refresh');
this.reset();
this.refresh();
this.runDataProvider.refresh(workspaceItem);
}),
vscode.commands.registerCommand('terraform.cloud.workspaces.resetProjectFilter', () => {
this.reporter.sendTelemetryEvent('tfc-workspaces-filter-reset');
this.projectFilter = undefined;
this.reset();
this.refresh();
}),
vscode.commands.registerCommand(
Expand All @@ -58,13 +63,24 @@ export class WorkspaceTreeDataProvider implements vscode.TreeDataProvider<Worksp
this.reporter.sendTelemetryEvent('tfc-workspaces-filter');
this.filterByProject();
}),
vscode.commands.registerCommand('terraform.cloud.workspaces.loadMore', async () => {
this.reporter.sendTelemetryEvent('tfc-workspaces-loadMore');
this.refresh();
this.runDataProvider.refresh();
}),
);
}

refresh(): void {
this.didChangeTreeData.fire();
}

// This resets the internal cache, e.g. after logout
reset(): void {
this.nextPage = null;
this.cache = [];
}

async filterByProject(): Promise<void> {
const session = await vscode.authentication.getSession(TerraformCloudAuthenticationProvider.providerID, [], {
createIfNone: false,
Expand All @@ -86,28 +102,40 @@ export class WorkspaceTreeDataProvider implements vscode.TreeDataProvider<Worksp
this.projectFilter = project.description;
await vscode.commands.executeCommand('setContext', 'terraform.cloud.projectFilterUsed', true);
}
this.reset();
this.refresh();
this.runDataProvider.refresh();
}

getTreeItem(element: WorkspaceTreeItem): vscode.TreeItem | Thenable<vscode.TreeItem> {
getTreeItem(element: vscode.TreeItem): vscode.TreeItem | Thenable<vscode.TreeItem> {
return element;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
getChildren(element?: any): vscode.ProviderResult<WorkspaceTreeItem[]> {
getChildren(element?: any): vscode.ProviderResult<vscode.TreeItem[]> {
if (element) {
return [element];
}

return this.buildChildren();
}

private async buildChildren() {
try {
return this.getWorkspaces();
this.cache = [...this.cache, ...(await this.getWorkspaces())];
} catch (error) {
return [];
}

const items = this.cache.slice(0);
if (this.nextPage !== null) {
items.push(new LoadMoreTreeItem());
}

return items;
}

private async getWorkspaces() {
private async getWorkspaces(): Promise<vscode.TreeItem[]> {
const organization = this.ctx.workspaceState.get('terraform.cloud.organization', '');
if (organization === '') {
return [];
Expand All @@ -130,10 +158,12 @@ export class WorkspaceTreeDataProvider implements vscode.TreeDataProvider<Worksp
include: ['current_run'],
// Include query parameter only if project filter is set
...(this.projectFilter && { 'filter[project][id]': this.projectFilter }),
'page[size]': 50,
'page[size]': this.pageSize,
'page[number]': this.nextPage ?? 1,
sort: '-current-run.created-at',
},
});
this.nextPage = workspaceResponse.meta.pagination['next-page'];

this.reporter.sendTelemetryEvent('tfc-fetch-workspaces', undefined, {
totalCount: workspaceResponse.meta.pagination['total-count'],
Expand Down Expand Up @@ -254,6 +284,18 @@ export class WorkspaceTreeDataProvider implements vscode.TreeDataProvider<Worksp
}
}

export class LoadMoreTreeItem extends vscode.TreeItem {
constructor() {
super('Load more...', vscode.TreeItemCollapsibleState.None);

this.iconPath = new vscode.ThemeIcon('ellipsis', new vscode.ThemeColor('charts.gray'));
this.command = {
command: 'terraform.cloud.workspaces.loadMore',
title: 'Load more',
};
}
}

export class WorkspaceTreeItem extends vscode.TreeItem {
/**
* @param name The Workspace Name
Expand All @@ -272,6 +314,7 @@ export class WorkspaceTreeItem extends vscode.TreeItem {

this.description = `[${this.projectName}]`;
this.iconPath = GetRunStatusIcon(this.lastRun?.status);
this.contextValue = 'hasLink';

const lockedTxt = this.attributes.locked ? '$(lock) Locked' : '$(unlock) Unlocked';
const vscText =
Expand Down

0 comments on commit 8ef19f4

Please sign in to comment.