Skip to content

Commit

Permalink
Merge pull request #483 from weaveworks/toolkit-node
Browse files Browse the repository at this point in the history
factor out toolkit node. loading spinners
  • Loading branch information
Kingdon Barrett authored Sep 15, 2023
2 parents fa62815 + 19a8ac4 commit 9c82fcc
Show file tree
Hide file tree
Showing 14 changed files with 134 additions and 241 deletions.
1 change: 1 addition & 0 deletions src/ui/treeviews/dataProviders/sourceDataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export class SourceDataProvider extends KubernetesObjectDataProvider {

for (const helmRepository of sortByMetadataName(helmRepositories)) {
sourceNodes.push(new HelmRepositoryNode(helmRepository));
const x = new HelmRepositoryNode(helmRepository);
}

// add buckets to the tree
Expand Down
2 changes: 1 addition & 1 deletion src/ui/treeviews/dataProviders/workloadDataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export class WorkloadDataProvider extends KubernetesObjectDataProvider {
* @param workloadNode target workload node
*/
async updateWorkloadChildren(workloadNode: WorkloadNode) {
workloadNode.children = [new TreeNode('Loading...')];
workloadNode.children = infoNodes(InfoNode.Loading);

if (workloadNode instanceof KustomizationNode) {
this.updateKustomizationChildren(workloadNode);
Expand Down
10 changes: 8 additions & 2 deletions src/ui/treeviews/nodes/namespaceNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,25 @@ export class NamespaceNode extends TreeNode {
updateLabel(withIcons = true) {
const totalLength = this.children.length;
let readyLength = 0;
let loadingLength = 0;
for(const child of this.children) {
if(child instanceof SourceNode || child instanceof WorkloadNode) {
if(!child.isReconcileFailed) {
if(child.resourceIsReady) {
readyLength++;
} else if(child.resourceIsProgressing) {
loadingLength++;
}
} else {
readyLength++;
}
}

const validLength = readyLength + loadingLength;
if(withIcons) {
if(readyLength === totalLength) {
this.setIcon(TreeNodeIcon.Success);
} else if(validLength === totalLength) {
this.setIcon(TreeNodeIcon.Progressing);
} else {
this.setIcon(TreeNodeIcon.Warning);
}
Expand All @@ -50,7 +56,7 @@ export class NamespaceNode extends TreeNode {
}

if(this.collapsibleState === TreeItemCollapsibleState.Collapsed) {
const lengthLabel = totalLength === readyLength ? `${totalLength}` : `${readyLength}/${totalLength}`;
const lengthLabel = totalLength === validLength ? `${totalLength}` : `${validLength}/${totalLength}`;
this.label = `${this.resource.metadata?.name} (${lengthLabel})`;
} else {
this.label = `${this.resource.metadata?.name}`;
Expand Down
13 changes: 1 addition & 12 deletions src/ui/treeviews/nodes/source/bucketNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,10 @@ import { SourceNode } from './sourceNode';
* Defines Bucket tree view item for display in GitOps Sources tree view.
*/
export class BucketNode extends SourceNode {

/**
* Bucket kubernetes resource object
*/
resource: Bucket;

/**
* Creates new bucket tree view item for display.
* @param bucket Bucket kubernetes object info.
*/
constructor(bucket: Bucket) {
super(`${Kind.Bucket}: ${bucket.metadata?.name}`, bucket);

this.resource = bucket;
}
resource!: Bucket;

get contexts() {
return [Kind.Bucket];
Expand Down
15 changes: 1 addition & 14 deletions src/ui/treeviews/nodes/source/gitRepositoryNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,8 @@ import { SourceNode } from './sourceNode';
* Defines GitRepository tree view item for display in GitOps Sources tree view.
*/
export class GitRepositoryNode extends SourceNode {
resource!: GitRepository;

/**
* Git repository kubernetes resource object
*/
resource: GitRepository;

/**
* Creates new git repository tree view item for display.
* @param gitRepository Git repository kubernetes object info.
*/
constructor(gitRepository: GitRepository) {
super(`${Kind.GitRepository}: ${gitRepository.metadata?.name}`, gitRepository);

this.resource = gitRepository;
}

get contexts() {
const contextsArr: string[] = [Kind.GitRepository];
Expand Down
15 changes: 1 addition & 14 deletions src/ui/treeviews/nodes/source/helmRepositoryNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,8 @@ import { SourceNode } from './sourceNode';
* Defines HelmRepository tree view item for display in GitOps Sources tree view.
*/
export class HelmRepositoryNode extends SourceNode {
resource!: HelmRepository;

/**
* Helm repository kubernetes resource object
*/
resource: HelmRepository;

/**
* Creates new helm repository tree view item for display.
* @param helmRepository Helm repository kubernetes object info.
*/
constructor(helmRepository: HelmRepository) {
super(`${Kind.HelmRepository}: ${helmRepository.metadata?.name}`, helmRepository);

this.resource = helmRepository;
}

get contexts() {
const contextsArr: string[] = [Kind.HelmRepository];
Expand Down
10 changes: 2 additions & 8 deletions src/ui/treeviews/nodes/source/ociRepositoryNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,14 @@ import { SourceNode } from './sourceNode';
* Defines OCIRepository tree view item for display in GitOps Sources tree view.
*/
export class OCIRepositoryNode extends SourceNode {

/**
* OCI repository kubernetes resource object
*/
resource: OCIRepository;
resource!: OCIRepository;

/**
* Creates new oci repository tree view item for display.
* @param ociRepository OCI repository kubernetes object info.
*/
constructor(ociRepository: OCIRepository) {
super(`${Kind.OCIRepository}: ${ociRepository.metadata?.name}`, ociRepository);

this.resource = ociRepository;
super(ociRepository);
}

get contexts() {
Expand Down
89 changes: 5 additions & 84 deletions src/ui/treeviews/nodes/source/sourceNode.ts
Original file line number Diff line number Diff line change
@@ -1,94 +1,15 @@
import { MarkdownString } from 'vscode';

import { Bucket } from 'types/flux/bucket';
import { GitRepository } from 'types/flux/gitRepository';
import { HelmRepository } from 'types/flux/helmRepository';
import { FluxSourceObject } from 'types/flux/object';
import { OCIRepository } from 'types/flux/ociRepository';
import { Condition } from 'types/kubernetes/kubernetesTypes';
import { createMarkdownError, createMarkdownHr, createMarkdownTable } from 'utils/markdownUtils';
import { shortenRevision } from 'utils/stringUtils';
import { TreeNode, TreeNodeIcon } from '../treeNode';

import { ToolkitNode } from './toolkitNode';
/**
* Base class for all the Source tree view items.
*/
export class SourceNode extends TreeNode {

resource: FluxSourceObject;

/**
* Whether or not the source failed to reconcile.
*/
isReconcileFailed = false;

constructor(label: string, source: FluxSourceObject) {
super(label);

this.resource = source;

// update reconciliation status
this.updateStatus();
}

get tooltip() {
return this.getMarkdownHover(this.resource);
}
export class SourceNode extends ToolkitNode {
resource!: FluxSourceObject;

// @ts-ignore
get description() {
const isSuspendIcon = this.resource.spec?.suspend ? '⏸ ' : '';
let revisionOrError = '';

if (this.isReconcileFailed) {
revisionOrError = `${this.findReadyOrFirstCondition(this.resource.status.conditions)?.reason || ''}`;
} else {
revisionOrError = shortenRevision(this.resource.status.artifact?.revision);
}

return `${isSuspendIcon}${revisionOrError}`;
get revision() {
return shortenRevision(this.resource.status.artifact?.revision);
}

/**
* Creates markdwon string for Source tree view item tooltip.
* @param source GitRepository, HelmRepository or Bucket kubernetes object.
* @returns Markdown string to use for Source tree view item tooltip.
*/
getMarkdownHover(source: GitRepository | OCIRepository | HelmRepository | Bucket): MarkdownString {
const markdown: MarkdownString = createMarkdownTable(source);

// show status in hover when source fetching failed
if (this.isReconcileFailed) {
const readyCondition = this.findReadyOrFirstCondition(source.status.conditions);
createMarkdownHr(markdown);
createMarkdownError('Status message', readyCondition?.message, markdown);
createMarkdownError('Status reason', readyCondition?.reason, markdown);
}

return markdown;
}

/**
* Find condition with the "Ready" type or
* return first one if "Ready" not found.
*
* @param conditions "status.conditions" of the source
*/
findReadyOrFirstCondition(conditions?: Condition[]): Condition | undefined {
return conditions?.find(condition => condition.type === 'Ready') || conditions?.[0];
}

/**
* Update source status with showing error icon when fetch failed.
* @param source target source
*/
updateStatus(): void {
if (this.findReadyOrFirstCondition(this.resource.status.conditions)?.status === 'True') {
this.setIcon(TreeNodeIcon.Success);
this.isReconcileFailed = false;
} else {
this.setIcon(TreeNodeIcon.Error);
this.isReconcileFailed = true;
}
}
}
92 changes: 92 additions & 0 deletions src/ui/treeviews/nodes/source/toolkitNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { FluxObject } from 'types/flux/object';
import { Condition } from 'types/kubernetes/kubernetesTypes';
import { createMarkdownError, createMarkdownHr, createMarkdownTable } from 'utils/markdownUtils';
import { TreeNode, TreeNodeIcon } from '../treeNode';

export enum ReconcileState {
Ready,
Failed,
Progressing,
}

export class ToolkitNode extends TreeNode {
resource: FluxObject;
reconcileState: ReconcileState = ReconcileState.Progressing;

constructor(resource: FluxObject) {
super(`${resource.kind}: ${resource.metadata?.name || 'unknown'}`);

this.resource = resource;
this.updateStatus();
}

/**
* Update status with showing error icon when fetch failed.
* @param source target source
*/
updateStatus() {
const condition = this.readyOrFirstCondition;
if (condition?.status === 'True') {
this.reconcileState = ReconcileState.Ready;
this.setIcon(TreeNodeIcon.Success);
} else if (condition?.reason === 'Progressing') {
this.reconcileState = ReconcileState.Progressing;
this.setIcon(TreeNodeIcon.Progressing);
} else {
this.reconcileState = ReconcileState.Failed;
this.setIcon(TreeNodeIcon.Error);
}
}

/**
* Find condition with the "Ready" type or
* return first one if "Ready" not found.
*/
get readyOrFirstCondition(): Condition | undefined {
const conditions = this.resource.status.conditions;

if (Array.isArray(conditions)) {
return conditions.find(condition => condition.type === 'Ready') || conditions[0];
} else {
return conditions;
}
}

get tooltip() {
const markdown = createMarkdownTable(this.resource);
// show status in hoverwhat failed
if (!this.resourceIsReady) {
createMarkdownHr(markdown);
createMarkdownError('Status message', this.readyOrFirstCondition?.message, markdown);
createMarkdownError('Status reason', this.readyOrFirstCondition?.reason, markdown);
}

return markdown;
}

get resourceIsReady(): boolean {
return this.reconcileState === ReconcileState.Ready;
}

get resourceIsProgressing(): boolean {
return this.reconcileState === ReconcileState.Progressing;
}

// @ts-ignore
get description() {
const isSuspendIcon = this.resource.spec?.suspend ? '⏸ ' : '';
let revisionOrError = '';

if (!this.resourceIsReady) {
revisionOrError = `${this.readyOrFirstCondition?.reason || ''}`;
} else {
revisionOrError = this.revision;
}

return `${isSuspendIcon}${revisionOrError}`;
}

get revision(): string {
return 'unknown';
}
}
6 changes: 6 additions & 0 deletions src/ui/treeviews/nodes/treeNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export const enum TreeNodeIcon {
Warning = 'warning',
Success = 'success',
Disconnected = 'disconnected',
Progressing = 'progressing',
Loading = 'loading',
Unknown = 'unknown',
}

Expand Down Expand Up @@ -77,6 +79,10 @@ export class TreeNode extends TreeItem {
this.iconPath = new ThemeIcon('warning', new ThemeColor('editorWarning.foreground'));
} else if (icon === TreeNodeIcon.Disconnected) {
this.iconPath = new ThemeIcon('sync-ignored', new ThemeColor('editorError.foreground'));
} else if (icon === TreeNodeIcon.Progressing) {
this.iconPath = new ThemeIcon('sync~spin', new ThemeColor('terminal.ansiGreen'));
} else if (icon === TreeNodeIcon.Loading) {
this.iconPath = new ThemeIcon('loading~spin', new ThemeColor('foreground'));
} else if (icon === TreeNodeIcon.Success) {
this.iconPath = new ThemeIcon('pass', new ThemeColor('terminal.ansiGreen'));
} else if (icon === TreeNodeIcon.Unknown) {
Expand Down
10 changes: 2 additions & 8 deletions src/ui/treeviews/nodes/workload/helmReleaseNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,14 @@ import { WorkloadNode } from './workloadNode';
* Defines Helm release tree view item for display in GitOps Workloads tree view.
*/
export class HelmReleaseNode extends WorkloadNode {

/**
* Helm release kubernetes resource object
*/
resource: HelmRelease;
resource!: HelmRelease;

/**
* Creates new helm release tree view item for display.
* @param helmRelease Helm release kubernetes object info.
*/
constructor(helmRelease: HelmRelease) {
super(helmRelease.metadata?.name || '', helmRelease);

this.resource = helmRelease;
super(helmRelease);

this.makeCollapsible();
}
Expand Down
Loading

0 comments on commit 9c82fcc

Please sign in to comment.