Skip to content

Commit

Permalink
feat: Extend progress stepper results
Browse files Browse the repository at this point in the history
This changes the existing work on `ResultProgressStep` to include
status handling for cancelled and failed. Further improving UX by
ensuring that the status reporting is consistent across the page
  • Loading branch information
Venefilyn committed Jun 4, 2024
1 parent 17cf0b6 commit f87fb35
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 13 deletions.
94 changes: 85 additions & 9 deletions frontend/src/app/Results/ResultProgressStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,81 @@
// SPDX-License-Identifier: MIT

import React, { useMemo } from "react";
import { ProgressStepper, ProgressStep, Tooltip } from "@patternfly/react-core";
import {
ProgressStepper,
ProgressStep,
Tooltip,
ProgressStepProps,
} from "@patternfly/react-core";
import { Timestamp } from "../utils/Timestamp";
import { prettyFormat } from "@imranbarbhuiya/duration";
import {
AngleDoubleRightIcon,
BanIcon,
QuestionCircleIcon,
SkypeIcon,
} from "@patternfly/react-icons";

/**
* Different statuses that we should map within the component
*/
export type AcceptedStatuses =
| "success"
| "fail"
| "skipped"
| "cancelled"
| "unknown";

/**
* The icon and name that we will display to the user. Icon is taken from
* PatternFly's ProgressStep directly and is thus inherited. Name is always a
* lowercase word
*/
type StepStatus = {
// Inherited from PatternFly
variant: NonNullable<ProgressStepProps["variant"]>;
// Always lowercase and current-tense, such as "[the build] Failed"
name: Lowercase<string>;
icon?: React.ReactNode;
};

/**
* Key-value mapping of the accepted statuses that we will map within the
* component, as well as an object value to contain the wording we will use and
* the icon
*/
export const ResultProgressStepStatus: Record<AcceptedStatuses, StepStatus> = {
success: {
variant: "success",
name: "successful",
},
fail: {
variant: "danger",
name: "failed",
},
skipped: {
variant: "pending",
name: "skipped",
icon: <AngleDoubleRightIcon />,
},
cancelled: {
variant: "danger",
name: "cancelled",
icon: <BanIcon />,
},
unknown: {
variant: "info",
name: "unknown",
icon: <QuestionCircleIcon />,
},
};

export interface ResultProgressStepProps {
submittedTime: number;
startTime?: number;
finishedTime?: number;
noun?: string;
status?: keyof typeof ResultProgressStepStatus;
}

/**
Expand All @@ -19,18 +85,14 @@ export interface ResultProgressStepProps {
* build itself. However, this can be more versatile if need be
* as we can change to a higher level component and use React contexts
*
* TODO (spytec): When/If we have consolidated the list of different
* statuses between our results, such as a Results utility to help
* us decide if a build/run is successful, in progress, failed, or
* has warnings. Then we can incorporate that into this component to indicate where the failure is.
*
* @author Freya Gustavsson <[email protected]>
* @author Freya Gustavsson <[email protected]>
*/
export const ResultProgressStep: React.FC<ResultProgressStepProps> = ({
noun = "Build",
submittedTime,
startTime,
finishedTime,
status = "success",
}) => {
const submittedStep = useMemo(() => {
const isCurrent = !startTime;
Expand Down Expand Up @@ -65,7 +127,16 @@ export const ResultProgressStep: React.FC<ResultProgressStepProps> = ({
return (
<ProgressStep
isCurrent={isCurrent}
variant={isPending ? "pending" : isCurrent ? "info" : "success"}
variant={
isPending
? "pending"
: isCurrent
? "info"
: ResultProgressStepStatus[status].variant
}
icon={
finishedTimeEpoch ? ResultProgressStepStatus[status].icon : undefined
}
id="step-submitted"
description={
startTime && finishedTime ? (
Expand Down Expand Up @@ -95,7 +166,12 @@ export const ResultProgressStep: React.FC<ResultProgressStepProps> = ({
titleId="step-submitted-title"
aria-label="has been submitted"
>
{noun} {isPending ? "Pending" : isCurrent ? "In Progress" : "Done"}
{noun}{" "}
{isPending
? "Pending"
: isCurrent
? "In Progress"
: ResultProgressStepStatus[status].name}
</ProgressStep>
);
}, [startTime, finishedTime, noun]);
Expand Down
22 changes: 21 additions & 1 deletion frontend/src/app/Results/ResultsPageCoprDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { CoprResult } from "./ResultsPageCopr";
import React from "react";
import { StatusLabel } from "../StatusLabel/StatusLabel";
import { LabelLink } from "../utils/LabelLink";
import { ResultProgressStep } from "./ResultProgressStep";
import { AcceptedStatuses, ResultProgressStep } from "./ResultProgressStep";

export interface ResultsPageCoprDetailsProps {
data: CoprResult;
Expand All @@ -28,6 +28,25 @@ export const ResultsPageCoprDetails: React.FC<ResultsPageCoprDetailsProps> = ({
"Logs not available"
);

/**
* Map the different statuses of Copr builds to the visual aspect
*
* TODO (@Venefilyn): change the statuses to match API
*
* @param {string} status - list of statuses from Copr builds
* @return {*} {AcceptedStatuses}
*/
function getCoprBuildStatus(status: string): AcceptedStatuses {
switch (status) {
case "failure":
return "fail";
case "success":
return "success";
default:
return "unknown";
}
}

return (
<DescriptionList
columnModifier={{
Expand Down Expand Up @@ -61,6 +80,7 @@ export const ResultsPageCoprDetails: React.FC<ResultsPageCoprDetailsProps> = ({
submittedTime={data.build_submitted_time}
startTime={data.build_start_time}
finishedTime={data.build_finished_time}
status={getCoprBuildStatus(data.status)}
/>
</DescriptionListDescription>
</DescriptionListGroup>
Expand Down
22 changes: 21 additions & 1 deletion frontend/src/app/Results/ResultsPageKoji.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { useParams } from "react-router-dom";
import { useTitle } from "../utils/useTitle";
import { useQuery } from "@tanstack/react-query";
import { SHACopy } from "../utils/SHACopy";
import { ResultProgressStep } from "./ResultProgressStep";
import { AcceptedStatuses, ResultProgressStep } from "./ResultProgressStep";

interface KojiBuild {
scratch: boolean;
Expand Down Expand Up @@ -65,6 +65,25 @@ const ResultsPageKoji = () => {
KojiBuild | { error: string }
>([URL], () => fetchKojiBuilds(URL));

/**
* Map the different statuses of Koji builds to the visual aspect
*
* TODO (@Venefilyn): change the statuses to match API
*
* @param {string} status - list of statuses from Koji builds
* @return {*} {AcceptedStatuses}
*/
function getKojiBuildStatus(status: string): AcceptedStatuses {
switch (status) {
case "failure":
return "fail";
case "success":
return "success";
default:
return "unknown";
}
}

// If backend API is down
if (isError) {
return <ErrorConnection />;
Expand Down Expand Up @@ -172,6 +191,7 @@ const ResultsPageKoji = () => {
submittedTime={data.build_submitted_time}
startTime={data.build_start_time}
finishedTime={data.build_finished_time}
status={getKojiBuildStatus(data.status)}
/>
</DescriptionListDescription>
</DescriptionListGroup>
Expand Down
22 changes: 21 additions & 1 deletion frontend/src/app/Results/ResultsPageSRPM.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { StatusLabel } from "../StatusLabel/StatusLabel";
import { useParams } from "react-router-dom";
import { useTitle } from "../utils/useTitle";
import { useQuery } from "@tanstack/react-query";
import { ResultProgressStep } from "./ResultProgressStep";
import { AcceptedStatuses, ResultProgressStep } from "./ResultProgressStep";

interface SRPMBuild {
status: string;
Expand Down Expand Up @@ -66,6 +66,25 @@ const ResultsPageSRPM = () => {
SRPMBuild | { error: string }
>([URL], () => fetchSRPMBuild(URL));

/**
* Map the different statuses of SRPM builds to the visual aspect
*
* TODO (@Venefilyn): change the statuses to match API
*
* @param {string} status - list of statuses from SRPM builds
* @return {*} {AcceptedStatuses}
*/
function getSRPMStatus(status: string): AcceptedStatuses {
switch (status) {
case "failure":
return "fail";
case "success":
return "success";
default:
return "unknown";
}
}

// If backend API is down
if (isError) {
return <ErrorConnection />;
Expand Down Expand Up @@ -179,6 +198,7 @@ const ResultsPageSRPM = () => {
submittedTime={data.build_submitted_time}
startTime={data.build_start_time}
finishedTime={data.build_finished_time}
status={getSRPMStatus(data.status)}
/>
</DescriptionListDescription>
</DescriptionListGroup>
Expand Down
24 changes: 23 additions & 1 deletion frontend/src/app/Results/ResultsPageSyncReleaseRuns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { useParams } from "react-router-dom";
import { useTitle } from "../utils/useTitle";
import { useQuery } from "@tanstack/react-query";
import { DownloadIcon, ExpandIcon } from "@patternfly/react-icons";
import { ResultProgressStep } from "./ResultProgressStep";
import { ResultProgressStep, AcceptedStatuses } from "./ResultProgressStep";

interface ResultsPageSyncReleaseRunsProps {
job: "propose-downstream" | "pull-from-upstream";
Expand Down Expand Up @@ -242,6 +242,27 @@ const ResultsPageSyncReleaseRuns: React.FC<ResultsPageSyncReleaseRunsProps> = ({
</PageSection>
);

/**
* Map the different statuses of sync release runs to the visual aspect
*
* TODO (@Venefilyn): change the statuses to match API
*
* @param {string} status - list of statuses from sync release
* @return {*} {AcceptedStatuses}
*/
function getSyncReleaseStatus(status: string): AcceptedStatuses {
switch (status) {
case "error":
return "fail";
case "successful":
return "success";
case "skipped":
return "skipped";
default:
return "unknown";
}
}

return (
<>
<PageSection variant={PageSectionVariants.light}>
Expand Down Expand Up @@ -293,6 +314,7 @@ const ResultsPageSyncReleaseRuns: React.FC<ResultsPageSyncReleaseRunsProps> = ({
submittedTime={data.submitted_time}
startTime={data.start_time}
finishedTime={data.finished_time}
status={getSyncReleaseStatus(data.status)}
/>
</DescriptionListDescription>
</DescriptionListGroup>
Expand Down

0 comments on commit f87fb35

Please sign in to comment.