Skip to content
12 changes: 6 additions & 6 deletions frontend/src/__mocks__/mockWorkloadK8sResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const mockWorkloadStatusConditions: Record<WorkloadStatusType, WorkloadCondition
type: 'Evicted',
},
],
Succeeded: [
Complete: [
{
lastTransitionTime: '2024-03-18T19:15:28Z',
message: 'Quota reserved in ClusterQueue cluster-queue',
Expand Down Expand Up @@ -124,9 +124,9 @@ const mockWorkloadStatusConditions: Record<WorkloadStatusType, WorkloadCondition
],
};

const mockWorkloadEmptySucceedCondition: Record<WorkloadStatusType.Succeeded, WorkloadCondition[]> =
const mockWorkloadEmptyCompleteCondition: Record<WorkloadStatusType.Complete, WorkloadCondition[]> =
{
[WorkloadStatusType.Succeeded]: [
[WorkloadStatusType.Complete]: [
{
lastTransitionTime: '2024-03-18T19:15:28Z',
message: 'Quota reserved in ClusterQueue cluster-queue',
Expand Down Expand Up @@ -164,7 +164,7 @@ export const mockWorkloadK8sResource = ({
namespace = 'test-project',
ownerKind = WorkloadOwnerType.Job,
ownerName,
mockStatus = WorkloadStatusType.Succeeded,
mockStatus = WorkloadStatusType.Complete,
podSets = [],
mockStatusEmptyWorkload = false,
}: MockResourceConfigType): WorkloadKind => ({
Expand Down Expand Up @@ -203,8 +203,8 @@ export const mockWorkloadK8sResource = ({
},
status: {
conditions: mockStatus
? mockStatusEmptyWorkload
? mockWorkloadEmptySucceedCondition[WorkloadStatusType.Succeeded]
? mockStatusEmptyWorkload && mockStatus === WorkloadStatusType.Complete
? mockWorkloadEmptyCompleteCondition[WorkloadStatusType.Complete]
: mockWorkloadStatusConditions[mockStatus]
Comment thread
coderabbitai[bot] marked this conversation as resolved.
: [],
},
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/components/DocCardBadges.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { Label, LabelGroup, Tooltip } from '@patternfly/react-core';
import { SyncAltIcon, CheckCircleIcon, ExclamationCircleIcon } from '@patternfly/react-icons';
import { InProgressIcon, CheckCircleIcon, ExclamationCircleIcon } from '@patternfly/react-icons';
import { QuickStartContext, QuickStartContextValues } from '@patternfly/quickstarts';
import { OdhDocument, OdhDocumentType } from '#~/types';
import { getQuickStartCompletionStatus, CompletionStatusEnum } from '#~/utilities/quickStartUtils';
Expand Down Expand Up @@ -45,17 +45,17 @@ const DocCardBadges: React.FC<DocCardBadgesProps> = ({ odhDoc }) => {
</Label>
) : null}
{completionStatus === CompletionStatusEnum.InProgress ? (
<Label variant="outline" color="purple" icon={<SyncAltIcon />}>
<Label variant="outline" status="info" icon={<InProgressIcon />}>
In Progress
</Label>
) : null}
{completionStatus === CompletionStatusEnum.Success ? (
<Label variant="outline" color="green" icon={<CheckCircleIcon />}>
<Label variant="outline" status="success" icon={<CheckCircleIcon />}>
Complete
</Label>
) : null}
{completionStatus === CompletionStatusEnum.Failed ? (
<Label variant="outline" color="red" icon={<ExclamationCircleIcon />}>
<Label variant="outline" status="danger" icon={<ExclamationCircleIcon />}>
Failed
</Label>
) : null}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/OdhExploreCardTypeBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const OdhExploreCardTypeBadge: React.FC<OdhExploreCardTypeBadgeProps> = ({

return (
<Tooltip content={content}>
<Label className={isDisabled ? 'pf-m-disabled' : undefined} tabIndex={0} variant="outline">
<Label className={isDisabled ? 'pf-m-disabled' : undefined} tabIndex={0}>
{category}
</Label>
</Tooltip>
Expand Down
91 changes: 91 additions & 0 deletions frontend/src/components/__tests__/DocCardBadges.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import * as React from 'react';
import { render, screen } from '@testing-library/react';
import { QuickStartContext } from '@patternfly/quickstarts';
import { OdhDocument, OdhDocumentType } from '#~/types';
import { CompletionStatusEnum } from '#~/utilities/quickStartUtils';
import DocCardBadges from '#~/components/DocCardBadges';

jest.mock('#~/utilities/quickStartUtils', () => ({
...jest.requireActual('#~/utilities/quickStartUtils'),
getQuickStartCompletionStatus: jest.fn(),
}));

const mockGetQuickStartCompletionStatus = jest.mocked(
jest.requireMock<typeof import('#~/utilities/quickStartUtils')>('#~/utilities/quickStartUtils')
.getQuickStartCompletionStatus,
);

const createDoc = (
type: OdhDocumentType = OdhDocumentType.Documentation,
name = 'test-doc',
durationMinutes?: number,
): OdhDocument =>
({
metadata: { name },
spec: {
type,
displayName: 'Test Doc',
description: 'desc',
durationMinutes,
},
} as unknown as OdhDocument);

const renderWithContext = (odhDoc: OdhDocument) => {
const contextValue = {
allQuickStarts: [{ metadata: { name: odhDoc.metadata.name }, spec: { tasks: [] }, status: {} }],
activeQuickStartID: '',
allQuickStartStates: {},
setActiveQuickStart: jest.fn(),
restartQuickStart: jest.fn(),
setQuickStartTaskNumber: jest.fn(),
setQuickStartTaskStatus: jest.fn(),
setAllQuickStartStates: jest.fn(),
getQuickStartForId: jest.fn(),
};

return render(
<QuickStartContext.Provider value={contextValue as never}>
<DocCardBadges odhDoc={odhDoc} />
</QuickStartContext.Provider>,
);
};

describe('DocCardBadges', () => {
beforeEach(() => {
mockGetQuickStartCompletionStatus.mockReturnValue(undefined);
});

it('should render documentation type label', () => {
renderWithContext(createDoc(OdhDocumentType.Documentation));
expect(screen.getByText('Documentation')).toBeInTheDocument();
});

it('should render duration label when duration is provided', () => {
renderWithContext(createDoc(OdhDocumentType.Documentation, 'doc', 15));
expect(screen.getByText('15 minutes')).toBeInTheDocument();
});

it('should render "In Progress" label with info status for in-progress quickstart', () => {
mockGetQuickStartCompletionStatus.mockReturnValue(CompletionStatusEnum.InProgress);
renderWithContext(createDoc(OdhDocumentType.QuickStart, 'qs-in-progress'));
const inProgressLabel = screen.getByText('In Progress').closest('.pf-v6-c-label');
expect(inProgressLabel).toHaveClass('pf-m-info');
expect(inProgressLabel).toHaveClass('pf-m-outline');
});

it('should render "Complete" label with success status for completed quickstart', () => {
mockGetQuickStartCompletionStatus.mockReturnValue(CompletionStatusEnum.Success);
renderWithContext(createDoc(OdhDocumentType.QuickStart, 'qs-complete'));
const completeLabel = screen.getByText('Complete').closest('.pf-v6-c-label');
expect(completeLabel).toHaveClass('pf-m-success');
expect(completeLabel).toHaveClass('pf-m-outline');
});

it('should render "Failed" label with danger status for failed quickstart', () => {
mockGetQuickStartCompletionStatus.mockReturnValue(CompletionStatusEnum.Failed);
renderWithContext(createDoc(OdhDocumentType.QuickStart, 'qs-failed'));
const failedLabel = screen.getByText('Failed').closest('.pf-v6-c-label');
expect(failedLabel).toHaveClass('pf-m-danger');
expect(failedLabel).toHaveClass('pf-m-outline');
});
});
4 changes: 2 additions & 2 deletions frontend/src/concepts/__tests__/StartNotebookModal.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ describe('Start Notebook modal', () => {

// Validate the header contents
const header = screen.getByTestId('notebook-status-modal-header');
expect(header).toHaveTextContent('Workbench statusRunning');
expect(header).toHaveTextContent('Workbench statusReady');

// Validate the steps
const stepper = screen.getByTestId('notebook-startup-steps');
Expand All @@ -147,7 +147,7 @@ describe('Start Notebook modal', () => {

// Validate the header contents
const header = screen.getByTestId('notebook-status-modal-header');
expect(header).toHaveTextContent('Workbench statusRunning');
expect(header).toHaveTextContent('Workbench statusReady');

// Validate the steps
const stepper = screen.getByTestId('notebook-startup-steps');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,23 @@ describe('getStatusInfo', () => {
testWorkloadStatus(WorkloadStatusType.Admitted, 'The workload is admitted');
testWorkloadStatus(WorkloadStatusType.Running, 'The workload is running');
testWorkloadStatus(WorkloadStatusType.Evicted, 'The workload is evicted');
testWorkloadStatus(WorkloadStatusType.Succeeded, 'Job finished successfully');
testWorkloadStatus(WorkloadStatusType.Complete, 'Job finished successfully');
testWorkloadStatus(WorkloadStatusType.Failed, 'There was an error');
testWorkloadStatus(null, 'No message'); // Falls back to Pending with no conditions
});

it('provides correct info for completed workload', () => {
const wl = mockWorkloadK8sResource({ k8sName: 'test-workload' });
const info = getStatusInfo(wl);
expect(info.color).toBe('green');
expect(info.labelStatus).toBe('success');
expect(info.message).toBe('Job finished successfully');
expect(info.status).toBe('Succeeded');
expect(info.status).toBe('Complete');
});
it('should return "Finished" when status is Succeeded and message is "No message"', () => {
const wl = mockWorkloadK8sResource({
k8sName: 'test-workload',
mockStatusEmptyWorkload: true,
mockStatus: WorkloadStatusType.Succeeded,
mockStatus: WorkloadStatusType.Complete,
});
const info = getStatusInfo(wl);
expect(getWorkloadStatusMessage(info)).toEqual('Finished');
Expand All @@ -61,7 +61,7 @@ describe('getStatusCounts', () => {
const workloads = [
mockWorkloadK8sResource({
k8sName: 'test-workload',
mockStatus: WorkloadStatusType.Succeeded,
mockStatus: WorkloadStatusType.Complete,
}),
mockWorkloadK8sResource({
k8sName: 'test-workload-2',
Expand Down Expand Up @@ -100,7 +100,7 @@ describe('getStatusCounts', () => {
Pending: 2,
Running: 2,
Evicted: 1,
Succeeded: 1,
Complete: 1,
Failed: 1,
});
});
Expand Down
36 changes: 18 additions & 18 deletions frontend/src/concepts/distributedWorkloads/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import {
InProgressIcon,
CheckCircleIcon,
ExclamationCircleIcon,
UploadIcon,
BanIcon,
CheckIcon,
} from '@patternfly/react-icons';
import { SVGIconProps } from '@patternfly/react-icons/dist/esm/createIcon';
import {
Expand Down Expand Up @@ -42,7 +41,7 @@ export enum WorkloadStatusType {
Admitted = 'Admitted',
Running = 'Running',
Evicted = 'Evicted',
Succeeded = 'Succeeded',
Complete = 'Complete',
Failed = 'Failed',
}

Expand All @@ -55,47 +54,48 @@ export type TopWorkloadUsageType = {
export type WorkloadStatusInfo = {
status: WorkloadStatusType;
message: string;
color: LabelProps['color'];
color?: LabelProps['color'];
labelStatus?: LabelProps['status'];
chartColor: string;
icon: React.ComponentClass<SVGIconProps>;
};

export const WorkloadStatusColorAndIcon: Record<
WorkloadStatusType,
Pick<WorkloadStatusInfo, 'color' | 'chartColor' | 'icon'>
Pick<WorkloadStatusInfo, 'color' | 'labelStatus' | 'chartColor' | 'icon'>
> = {
Pending: {
color: 'teal',
color: 'purple',
chartColor: chartColorCyan.value,
icon: PendingIcon,
},
Inadmissible: {
color: 'orangered',
labelStatus: 'warning',
chartColor: chartColorGold.value,
icon: ExclamationTriangleIcon,
},
Admitted: {
color: 'purple',
color: 'grey',
chartColor: chartColorPurple.value,
icon: UploadIcon,
icon: CheckIcon,
},
Running: {
color: 'blue',
chartColor: chartColorBlue.value,
icon: InProgressIcon,
},
Evicted: {
color: 'grey',
labelStatus: 'warning',
chartColor: chartColorBlack.value,
icon: BanIcon,
icon: ExclamationTriangleIcon,
},
Succeeded: {
color: 'green',
Complete: {
labelStatus: 'success',
chartColor: chartColorGreen.value,
icon: CheckCircleIcon,
},
Failed: {
color: 'red',
labelStatus: 'danger',
chartColor: chartColorRed.value,
icon: ExclamationCircleIcon,
},
Expand All @@ -106,7 +106,7 @@ export const getStatusInfo = (wl: WorkloadKind): WorkloadStatusInfo => {
// Order matters here: The first matching condition in this order will be used for the current status.
const statusesInEvalOrder: WorkloadStatusType[] = [
WorkloadStatusType.Failed,
WorkloadStatusType.Succeeded,
WorkloadStatusType.Complete,
WorkloadStatusType.Evicted,
WorkloadStatusType.Inadmissible,
WorkloadStatusType.Pending,
Expand All @@ -120,7 +120,7 @@ export const getStatusInfo = (wl: WorkloadKind): WorkloadStatusInfo => {
type === 'Finished' &&
/error|failed|rejected/.test(`${message} ${reason}`.toLowerCase()),
),
Succeeded: conditions?.find(
Complete: conditions?.find(
({ type, status, message, reason }) =>
status === 'True' &&
type === 'Finished' &&
Expand Down Expand Up @@ -154,7 +154,7 @@ export const getStatusCounts = (workloads: WorkloadKind[]): WorkloadStatusCounts
Admitted: 0,
Running: 0,
Evicted: 0,
Succeeded: 0,
Complete: 0,
Failed: 0,
};
workloads.forEach((wl) => {
Expand Down Expand Up @@ -271,7 +271,7 @@ export const getWorkloadStatusMessage = (statusInfo: WorkloadStatusInfo): string
const DEFAULT_MESSAGE = 'No message';
const SUCCESS_MESSAGE = 'Finished';
const isSuccessWithNoMessage =
statusInfo.status === WorkloadStatusType.Succeeded && statusInfo.message === DEFAULT_MESSAGE;
statusInfo.status === WorkloadStatusType.Complete && statusInfo.message === DEFAULT_MESSAGE;

return isSuccessWithNoMessage ? SUCCESS_MESSAGE : statusInfo.message;
};
Loading
Loading