diff --git a/src/ui/treeviews/dataProviders/sourceDataProvider.ts b/src/ui/treeviews/dataProviders/sourceDataProvider.ts index 54e8600c..78743c5e 100644 --- a/src/ui/treeviews/dataProviders/sourceDataProvider.ts +++ b/src/ui/treeviews/dataProviders/sourceDataProvider.ts @@ -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 diff --git a/src/ui/treeviews/dataProviders/workloadDataProvider.ts b/src/ui/treeviews/dataProviders/workloadDataProvider.ts index b6c6bbba..bd7bc7f0 100644 --- a/src/ui/treeviews/dataProviders/workloadDataProvider.ts +++ b/src/ui/treeviews/dataProviders/workloadDataProvider.ts @@ -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); diff --git a/src/ui/treeviews/nodes/namespaceNode.ts b/src/ui/treeviews/nodes/namespaceNode.ts index 3513b81d..60560837 100644 --- a/src/ui/treeviews/nodes/namespaceNode.ts +++ b/src/ui/treeviews/nodes/namespaceNode.ts @@ -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); } @@ -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}`; diff --git a/src/ui/treeviews/nodes/source/bucketNode.ts b/src/ui/treeviews/nodes/source/bucketNode.ts index 351ec3e9..3754c156 100644 --- a/src/ui/treeviews/nodes/source/bucketNode.ts +++ b/src/ui/treeviews/nodes/source/bucketNode.ts @@ -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]; diff --git a/src/ui/treeviews/nodes/source/gitRepositoryNode.ts b/src/ui/treeviews/nodes/source/gitRepositoryNode.ts index 11eaf9c1..a3535c6e 100644 --- a/src/ui/treeviews/nodes/source/gitRepositoryNode.ts +++ b/src/ui/treeviews/nodes/source/gitRepositoryNode.ts @@ -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]; diff --git a/src/ui/treeviews/nodes/source/helmRepositoryNode.ts b/src/ui/treeviews/nodes/source/helmRepositoryNode.ts index aab22ed6..9358dd7d 100644 --- a/src/ui/treeviews/nodes/source/helmRepositoryNode.ts +++ b/src/ui/treeviews/nodes/source/helmRepositoryNode.ts @@ -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]; diff --git a/src/ui/treeviews/nodes/source/ociRepositoryNode.ts b/src/ui/treeviews/nodes/source/ociRepositoryNode.ts index 7787fa53..09cc9fdb 100644 --- a/src/ui/treeviews/nodes/source/ociRepositoryNode.ts +++ b/src/ui/treeviews/nodes/source/ociRepositoryNode.ts @@ -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() { diff --git a/src/ui/treeviews/nodes/source/sourceNode.ts b/src/ui/treeviews/nodes/source/sourceNode.ts index 365d28d0..d7a2ae04 100644 --- a/src/ui/treeviews/nodes/source/sourceNode.ts +++ b/src/ui/treeviews/nodes/source/sourceNode.ts @@ -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; - } - } } diff --git a/src/ui/treeviews/nodes/source/toolkitNode.ts b/src/ui/treeviews/nodes/source/toolkitNode.ts new file mode 100644 index 00000000..2e2491f7 --- /dev/null +++ b/src/ui/treeviews/nodes/source/toolkitNode.ts @@ -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'; + } +} diff --git a/src/ui/treeviews/nodes/treeNode.ts b/src/ui/treeviews/nodes/treeNode.ts index 9f5fbbfd..9dcac84c 100644 --- a/src/ui/treeviews/nodes/treeNode.ts +++ b/src/ui/treeviews/nodes/treeNode.ts @@ -12,6 +12,8 @@ export const enum TreeNodeIcon { Warning = 'warning', Success = 'success', Disconnected = 'disconnected', + Progressing = 'progressing', + Loading = 'loading', Unknown = 'unknown', } @@ -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) { diff --git a/src/ui/treeviews/nodes/workload/helmReleaseNode.ts b/src/ui/treeviews/nodes/workload/helmReleaseNode.ts index 2d283bfc..38165c67 100644 --- a/src/ui/treeviews/nodes/workload/helmReleaseNode.ts +++ b/src/ui/treeviews/nodes/workload/helmReleaseNode.ts @@ -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(); } diff --git a/src/ui/treeviews/nodes/workload/kustomizationNode.ts b/src/ui/treeviews/nodes/workload/kustomizationNode.ts index 45af035f..586355dc 100644 --- a/src/ui/treeviews/nodes/workload/kustomizationNode.ts +++ b/src/ui/treeviews/nodes/workload/kustomizationNode.ts @@ -1,5 +1,5 @@ -import { Kind } from 'types/kubernetes/kubernetesTypes'; import { Kustomization } from 'types/flux/kustomization'; +import { Kind } from 'types/kubernetes/kubernetesTypes'; import { NodeContext } from 'types/nodeContext'; import { WorkloadNode } from './workloadNode'; @@ -7,19 +7,14 @@ import { WorkloadNode } from './workloadNode'; * Defines Kustomization tree view item for display in GitOps Workload tree view. */ export class KustomizationNode extends WorkloadNode { - /** - * Kustomize kubernetes resource object - */ - resource: Kustomization; + resource!: Kustomization; /** * Creates new app kustomization tree view item for display. * @param kustomization Kustomize kubernetes object info. */ constructor(kustomization: Kustomization) { - super(kustomization.metadata?.name || '', kustomization); - - this.resource = kustomization; + super(kustomization); this.makeCollapsible(); } diff --git a/src/ui/treeviews/nodes/workload/workloadNode.ts b/src/ui/treeviews/nodes/workload/workloadNode.ts index d0dfb1d7..02853c22 100644 --- a/src/ui/treeviews/nodes/workload/workloadNode.ts +++ b/src/ui/treeviews/nodes/workload/workloadNode.ts @@ -1,97 +1,14 @@ -import { MarkdownString } from 'vscode'; - -import { HelmRelease } from 'types/flux/helmRelease'; -import { Kustomization } from 'types/flux/kustomization'; import { FluxWorkloadObject } from 'types/flux/object'; -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 '../source/toolkitNode'; /** * Base class for all Workload tree view items. */ -export class WorkloadNode extends TreeNode { - - /** - * Whether or not the application failed to reconcile. - */ - isReconcileFailed = false; - - resource: FluxWorkloadObject; - - constructor(label: string, resource: FluxWorkloadObject) { - super(`${resource.kind}: ${label}`); - - this.resource = resource; - - this.updateStatus(); - } - - /** - * Find condition with the "Ready" type or - * return first one if "Ready" not found. - * - * @param conditions "status.conditions" of the workload - */ - findReadyOrFirstCondition(conditions?: Condition | Condition[]): Condition | undefined { - if (Array.isArray(conditions)) { - return conditions.find(condition => condition.type === 'Ready') || conditions[0]; - } else { - return conditions; - } - } - - /** - * Update workload status with showing error icon when reconcile has failed. - * @param workload target resource - */ - updateStatus(): void { - const condition = this.findReadyOrFirstCondition(this.resource.status.conditions); - - if (condition?.status === 'True') { - this.isReconcileFailed = false; - this.setIcon(TreeNodeIcon.Success); - } else { - this.isReconcileFailed = true; - this.setIcon(TreeNodeIcon.Error); - } - } - - get tooltip() { - const md = this.getMarkdownHover(this.resource); - return md; - } - - // @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.lastAppliedRevision); - } - return `${isSuspendIcon}${revisionOrError}`; - } - - /** - * Creates markdwon string for Source tree view item tooltip. - * @param workload Kustomize or HelmRelease kubernetes object. - * @returns Markdown string to use for Source tree view item tooltip. - */ - getMarkdownHover(workload: Kustomization | HelmRelease): MarkdownString { - const markdown: MarkdownString = createMarkdownTable(workload); - - // show status in hover when source fetching failed - if (this.isReconcileFailed) { - const readyCondition = this.findReadyOrFirstCondition(workload.status.conditions); - createMarkdownHr(markdown); - createMarkdownError('Status message', readyCondition?.message, markdown); - createMarkdownError('Status reason', readyCondition?.reason, markdown); - } +export class WorkloadNode extends ToolkitNode { + resource!: FluxWorkloadObject; - return markdown; + get revision() { + return shortenRevision(this.resource.status.lastAppliedRevision); } } diff --git a/src/utils/makeTreeviewInfoNode.ts b/src/utils/makeTreeviewInfoNode.ts index 1e71a9b7..f2028821 100644 --- a/src/utils/makeTreeviewInfoNode.ts +++ b/src/utils/makeTreeviewInfoNode.ts @@ -25,9 +25,13 @@ export function infoNode(type: InfoNode) { case InfoNode.NoResources: return new TreeNode('No Resources'); case InfoNode.Loading: - return new TreeNode('Loading...'); + node = new TreeNode('Loading...'); + node.setIcon(TreeNodeIcon.Loading); + return node; case InfoNode.LoadingApi: - return new TreeNode('Loading API...'); + node = new TreeNode('Loading API...'); + node.setIcon(TreeNodeIcon.Loading); + return node; case InfoNode.ClusterUnreachable: const name = kubeConfig.currentContext; node = new TreeNode(`Cluster ${name} unreachable`);