diff --git a/packages/core/src/domain/IOrchestratedItem.ts b/packages/core/src/domain/IOrchestratedItem.ts index f342e6e638d..a5e158d87e3 100644 --- a/packages/core/src/domain/IOrchestratedItem.ts +++ b/packages/core/src/domain/IOrchestratedItem.ts @@ -16,6 +16,7 @@ export interface IOrchestratedItem extends ITimedItem { getValueFor: (k: string) => any; originalStatus: string; status: string; + failureMessages: string[]; failureMessage: string; isBuffered: boolean; isCompleted: boolean; diff --git a/packages/core/src/orchestratedItem/orchestratedItem.transformer.ts b/packages/core/src/orchestratedItem/orchestratedItem.transformer.ts index 22543bb8e9c..98912cb1ea0 100644 --- a/packages/core/src/orchestratedItem/orchestratedItem.transformer.ts +++ b/packages/core/src/orchestratedItem/orchestratedItem.transformer.ts @@ -75,8 +75,11 @@ export class OrchestratedItemTransformer { item.originalStatus = item.status; Object.defineProperties(item, { + failureMessages: { + get: (): string[] => this.getFailureMessages(item), + }, failureMessage: { - get: (): string => this.mergeExceptions(item), + get: (): string => this.getFailureMessagesAsString(item), }, isCompleted: { get: (): boolean => ['SUCCEEDED', 'SKIPPED'].includes(item.status), @@ -131,19 +134,23 @@ export class OrchestratedItemTransformer { }); } - private static mergeExceptions(item: any): string | null { - const exceptions = [ - this.getCustomException(item), - this.getGeneralException(item), - this.getOrchestrationException(item), - ].filter((it) => !!it); + private static getFailureMessagesAsString(item: any): string | null { + const exceptions = this.getFailureMessages(item); if (exceptions.length === 0) { return null; } return exceptions.join('\n\n'); } - private static getOrchestrationException(task: ITask): string { + private static getFailureMessages(task: ITask): string[] { + return [ + this.getCustomExceptionMessage(task), + ...this.getGeneralExceptionMessages(task), + this.getOrchestrationExceptionMessage(task), + ].filter((it) => !!it); + } + + private static getOrchestrationExceptionMessage(task: ITask): string { const katoTasks: any[] = task.getValueFor('kato.tasks'); if (katoTasks && katoTasks.length) { const failedTask: any = katoTasks.find((t) => t.status && t.status.failed); @@ -190,7 +197,7 @@ export class OrchestratedItemTransformer { return null; } - private static getCustomException(task: ITask): string { + private static getCustomExceptionMessage(task: ITask): string { const generalException: any = task.getValueFor('exception'); if (generalException) { if (generalException.exceptionType && generalException.exceptionType === 'LockFailureException') { @@ -200,16 +207,17 @@ export class OrchestratedItemTransformer { return null; } - private static getGeneralException(task: ITask): string { + private static getGeneralExceptionMessages(task: ITask): string[] { const generalException: any = task.getValueFor('exception'); if (generalException) { const errors = (generalException.details?.errors ?? []).filter((m: any) => !!m); if (errors.length) { - return errors.join('\n\n'); + return errors; } - return generalException.details?.error ?? null; + + return generalException.details?.error ? [generalException.details.error] : []; } - return null; + return []; } private static calculateRunningTime(item: IOrchestratedItem): () => number { diff --git a/packages/core/src/presentation/CollapsibleElement.tsx b/packages/core/src/presentation/CollapsibleElement.tsx index 1252bd1769e..d6cb87d62d7 100644 --- a/packages/core/src/presentation/CollapsibleElement.tsx +++ b/packages/core/src/presentation/CollapsibleElement.tsx @@ -12,9 +12,11 @@ export const CollapsibleElement: React.FC<{ maxHeight: number }> = ({ children, setIsOverflowing(contentRef.current.offsetHeight < contentRef.current.scrollHeight); }, []); - React.useEffect(() => { - checkIsOverflowing(); - }, [children, checkIsOverflowing]); + React.useLayoutEffect(() => { + setTimeout(() => { + checkIsOverflowing(); + }); + }, [checkIsOverflowing]); React.useEffect(() => { window.addEventListener('resize', checkIsOverflowing); diff --git a/packages/kubernetes/src/pipelines/stages/deployManifest/manifestStatus/DeployStatus.less b/packages/kubernetes/src/pipelines/stages/deployManifest/manifestStatus/DeployStatus.less new file mode 100644 index 00000000000..1288b309c0e --- /dev/null +++ b/packages/kubernetes/src/pipelines/stages/deployManifest/manifestStatus/DeployStatus.less @@ -0,0 +1,11 @@ +.deploy-status .collapsible-element { + margin-bottom: 20px; + + .alert { + margin-bottom: 0; + } + + .content { + padding: 0; + } +} diff --git a/packages/kubernetes/src/pipelines/stages/deployManifest/manifestStatus/DeployStatus.tsx b/packages/kubernetes/src/pipelines/stages/deployManifest/manifestStatus/DeployStatus.tsx index 2a09d588fe2..25170daa1d2 100644 --- a/packages/kubernetes/src/pipelines/stages/deployManifest/manifestStatus/DeployStatus.tsx +++ b/packages/kubernetes/src/pipelines/stages/deployManifest/manifestStatus/DeployStatus.tsx @@ -2,12 +2,14 @@ import { get } from 'lodash'; import React from 'react'; import type { IExecutionDetailsSectionProps, IManifest } from '@spinnaker/core'; -import { ExecutionDetailsSection, StageFailureMessage } from '@spinnaker/core'; +import { CollapsibleElement, ExecutionDetailsSection, StageFailureMessage } from '@spinnaker/core'; import { ManifestStatus } from './ManifestStatus'; import type { IStageManifest } from '../../../../manifest/manifest.service'; import { KubernetesManifestService } from '../../../../manifest/manifest.service'; +import './DeployStatus.less'; + export interface IManifestSubscription { id: string; unsubscribe: () => void; @@ -87,22 +89,28 @@ export class DeployStatus extends React.Component !!sub.manifest).map((sub) => sub.manifest); return ( - - - {manifests && ( -
-
-
- {manifests.map((manifest) => { - const uid = - manifest.manifest.metadata.uid || KubernetesManifestService.manifestIdentifier(manifest.manifest); - return ; - })} +
+ + {stage.failureMessages.map((failureMessage) => ( + + + + ))} + {!!manifests?.length && ( +
+
+
+ {manifests.map((manifest) => { + const uid = + manifest.manifest.metadata.uid || KubernetesManifestService.manifestIdentifier(manifest.manifest); + return ; + })} +
-
- )} - + )} + +
); } }