Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TFC workspace pagination #1587

Merged
merged 7 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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())];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I switch between organizations, I get a Element with id '<long_id> is already registered. I think we need to have the refresh call in the organization switcher somehow indicate we need to clear the cache?

} 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