Skip to content

Commit

Permalink
refactor buildMapper
Browse files Browse the repository at this point in the history
  • Loading branch information
gabalafou committed Mar 12, 2024
1 parent 7caa80f commit 30d7ffa
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 157 deletions.
19 changes: 11 additions & 8 deletions src/features/metadata/components/BuildDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,25 @@ import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
import IconButton from "@mui/material/IconButton";
import { useAppDispatch } from "../../../hooks";
import { currentBuildIdChanged } from "..";
import { Build } from "../../../common/models";
import { buildDatetimeStatus } from "../../../utils/helpers/buildMapper";

interface IBuildProps {
/**
* @param builds list of builds
* @param currentBuildId id of the current build
* @param onChangeStatus update the build status
* @param selectedBuildId id of the build selected from the dropdown
*/
builds: {
id: number;
name: string;
status: string;
}[];
builds: Build[];
currentBuildId: number;
selectedBuildId: number;
}

export const BuildDropdown = ({ builds, selectedBuildId }: IBuildProps) => {
export const BuildDropdown = ({
builds,
currentBuildId,
selectedBuildId
}: IBuildProps) => {
const dispatch = useAppDispatch();
const { palette } = useTheme();
const [open, setOpen] = useState(false);
Expand Down Expand Up @@ -92,7 +95,7 @@ export const BuildDropdown = ({ builds, selectedBuildId }: IBuildProps) => {
{builds
? builds.map(build => (
<MenuItem key={build.id} value={build.id}>
{build.name}
{buildDatetimeStatus(build, currentBuildId)}
</MenuItem>
))
: null}
Expand Down
74 changes: 38 additions & 36 deletions src/features/metadata/components/EnvBuildStatus.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,42 @@
import React from "react";
import { CircularProgress, Typography } from "@mui/material";
import { StyledMetadataItem } from "../../../styles/StyledMetadataItem";
import Link from "@mui/material/Link";
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
import { artifactBaseUrl } from "../../../utils/helpers";
import { Artifact, Build } from "../../../common/models";
import { PrefContext } from "../../../preferences";
import { StyledMetadataItem } from "../../../styles/StyledMetadataItem";
import artifactList from "../../../utils/helpers/artifact";
import { Artifact } from "../../../common/models";
import { buildMapper } from "../../../utils/helpers/buildMapper";
import { artifactBaseUrl } from "../../../utils/helpers/parseArtifactList";
import { buildStatus } from "../../../utils/helpers/buildMapper";

const LogLink = ({ logArtifact }: { logArtifact: Artifact }) => {
const pref = React.useContext(PrefContext);
const url = new URL(
logArtifact.route,
artifactBaseUrl(pref.apiUrl, window.location.origin)
);
return (
<Link
href={url.toString()}
target="_blank"
sx={{
display: "inline-flex",
verticalAlign: "bottom", // align link (icon plus link text) with non-link text on the same line
alignItems: "center" // align icon and text within link
}}
>
<OpenInNewIcon sx={{ mr: 0.5 }} fontSize="inherit" />
Log
</Link>
);
};

interface IEnvBuildStatusProps {
// The build object here is a reduced and slightly modified version of the
// object described in common/models/Build.
build: ReturnType<typeof buildMapper>[0];
export interface IEnvBuildStatusProps {
build: Build;
}

export const EnvBuildStatus = ({ build }: IEnvBuildStatusProps) => {
// If the selected build is a failed build, we will render the link to the build log.
let logLink;
const showLogLink = build.status === "Failed";
const logArtifact: Artifact | never = artifactList(build.id, ["LOGS"])[0];
if (showLogLink && logArtifact) {
const pref = React.useContext(PrefContext);
const url = new URL(
logArtifact.route,
artifactBaseUrl(pref.apiUrl, window.location.origin)
);
logLink = (
<Link
href={url.toString()}
target="_blank"
sx={{
display: "inline-flex",
verticalAlign: "bottom", // align link (icon plus link text) with non-link text on the same line
alignItems: "center" // align icon and text within link
}}
>
<OpenInNewIcon sx={{ mr: 0.5 }} fontSize="inherit" />
Log
</Link>
);
}

return (
<StyledMetadataItem
Expand All @@ -54,18 +50,24 @@ export const EnvBuildStatus = ({ build }: IEnvBuildStatusProps) => {
>
Status: {""}
<Typography component="span" sx={{ fontSize: "13px" }}>
{build.status}
{buildStatus(build)}
{build.status_info && ` (${build.status_info})`}
{((build.status === "Building" || build.status === "Queued") && (
{build.status === "BUILDING" || build.status === "QUEUED" ? (
<CircularProgress
size={10}
sx={{
marginLeft: "8px"
}}
/>
)) ||
) : (
// If the selected build is a failed build, render the link to the build log.
(showLogLink && <>. {logLink}</>)}
build.status === "FAILED" &&
logArtifact && (
<>
. <LogLink logArtifact={logArtifact} />
</>
)
)}
</Typography>
</StyledMetadataItem>
);
Expand Down
10 changes: 6 additions & 4 deletions src/features/metadata/components/EnvBuilds.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { CircularProgress } from "@mui/material";
import { StyledMetadataItem } from "../../../styles/StyledMetadataItem";
import { Build as IBuild } from "../../../common/models";
import { BuildDropdown } from "../../../features/metadata/components";
import { buildMapper } from "../../../utils/helpers/buildMapper";
import { EnvBuildStatus } from "./EnvBuildStatus";

export interface IData {
Expand All @@ -19,8 +18,7 @@ export const EnvBuilds = ({
builds,
mode
}: IData) => {
const envBuilds = builds.length ? buildMapper(builds, currentBuildId) : [];
const selectedBuild = envBuilds.find(build => build.id === selectedBuildId);
const selectedBuild = builds.find(build => build.id === selectedBuildId);
return (
<>
<StyledMetadataItem
Expand All @@ -33,7 +31,11 @@ export const EnvBuilds = ({
</StyledMetadataItem>
{selectedBuild ? (
<>
<BuildDropdown builds={envBuilds} selectedBuildId={selectedBuildId} />
<BuildDropdown
builds={builds}
currentBuildId={currentBuildId}
selectedBuildId={selectedBuildId}
/>
<EnvBuildStatus build={selectedBuild} />
</>
) : (
Expand Down
80 changes: 30 additions & 50 deletions src/utils/helpers/buildMapper.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { format, utcToZonedTime } from "date-fns-tz";
import { Build } from "../../common/models";

const STATUS_OPTIONS: any = {
const STATUS_OPTIONS: { [key: string]: string } = {
COMPLETED: "Available",
QUEUED: "Queued",
FAILED: "Failed",
Expand All @@ -10,16 +10,6 @@ const STATUS_OPTIONS: any = {

const TIMEZONE = Intl.DateTimeFormat().resolvedOptions().timeZone;

const isBuilding = (status: string) => {
const BUILD_STATUS = ["BUILDING"];
return BUILD_STATUS.includes(status);
};

const isQueued = (status: string) => {
const BUILD_STATUS = ["QUEUED"];
return BUILD_STATUS.includes(status);
};

const isCompleted = (status: string, duration: number) => {
if (status === "COMPLETED") {
if (duration > 0) {
Expand All @@ -40,51 +30,41 @@ const dateToTimezone = (date: string) => {
});
};

export const buildMapper = (data: Build[], currentBuildId: number) => {
return data.map(
({ id, status, status_info, ended_on, scheduled_on }: Build) => {
export const buildDatetimeStatus = (
{ id, status, ended_on, scheduled_on }: Build,
currentBuildId: number
): string => {
if (id === currentBuildId) {
return `${dateToTimezone(ended_on ?? scheduled_on)} - Active`;
} else if (status === "BUILDING") {
return `${dateToTimezone(scheduled_on)} - Building`;
} else if (status === "QUEUED") {
return `${dateToTimezone(scheduled_on)} - Queued`;
} else {
return `${dateToTimezone(ended_on ?? scheduled_on)} - ${
STATUS_OPTIONS[status]
}`;
}
};

export const buildStatus = ({
status,
ended_on,
scheduled_on
}: Build): string => {
switch (status) {
case "BUILDING":
case "QUEUED":
return "Building";
default: {
let duration = 0;
if (ended_on && scheduled_on) {
const startTime = new Date(scheduled_on);
const endTime = new Date(ended_on);
duration = (endTime.valueOf() - startTime.valueOf()) / 60000;
duration = Math.round(duration);
}
if (id === currentBuildId) {
return {
id,
name: `${dateToTimezone(ended_on ?? scheduled_on)} - Active`,
status: isCompleted(status, duration),
status_info
};
}

if (isBuilding(status)) {
return {
id,
name: `${dateToTimezone(scheduled_on)} - Building`,
status: "Building",
status_info
};
}

if (isQueued(status)) {
return {
id,
name: `${dateToTimezone(scheduled_on)} - Queued`,
status: "Building",
status_info
};
}

return {
id,
name: `${dateToTimezone(ended_on ?? scheduled_on)} - ${
STATUS_OPTIONS[status]
}`,
status: isCompleted(status, duration),
status_info
};
return isCompleted(status, duration);
}
);
}
};
56 changes: 24 additions & 32 deletions test/helpers/BuildMapper.test.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,46 @@
import { buildMapper } from "../../src/utils/helpers";
import { buildDatetimeStatus } from "../../src/utils/helpers";
import { BUILD } from "../testutils";

const generateBuild = (status: string) => [
{
...BUILD,
status
}
];
describe("buildMapper", () => {
const generateBuild = (status: string) => ({
...BUILD,
status
});

describe("buildDatetimeStatus", () => {
it("should return an active build", () => {
const builds = generateBuild("COMPLETED");
const [mappedBuild] = buildMapper(builds, 1);
expect(mappedBuild.name).toContain("Active");
const build = generateBuild("COMPLETED");
expect(buildDatetimeStatus(build, 1)).toMatch(/Active$/);
});

it("should return build", () => {
const builds = generateBuild("BUILDING");
const [mappedBuild] = buildMapper(builds, 2);
expect(mappedBuild.name).toContain("Building");
const build = generateBuild("BUILDING");
expect(buildDatetimeStatus(build, 2)).toMatch(/Building$/);
});

it("should return queued build", () => {
const builds = generateBuild("QUEUED");
const [mappedBuild] = buildMapper(builds, 2);
expect(mappedBuild.name).toContain("Queued");
const build = generateBuild("QUEUED");
expect(buildDatetimeStatus(build, 2)).toMatch(/Queued$/);
});

it("should return completed build", () => {
const builds = generateBuild("COMPLETED");
const [mappedBuild] = buildMapper(builds, 2);
expect(mappedBuild.name).toContain("Available");
const build = generateBuild("COMPLETED");
expect(buildDatetimeStatus(build, 2)).toMatch(/Available$/);
});

it("should return failed build", () => {
const builds = generateBuild("FAILED");
const [mappedBuild] = buildMapper(builds, 2);
expect(mappedBuild.name).toContain("Failed");
const build = generateBuild("FAILED");
expect(buildDatetimeStatus(build, 2)).toMatch(/Failed$/);
});

it("should use the scheduled_on date if the ended_on prop is null", () => {
const [mappedBuild] = buildMapper(
[
{
...BUILD,
status: "FAILED",
ended_on: null
}
],
const datetimeStatus = buildDatetimeStatus(
{
...BUILD,
status: "FAILED",
ended_on: null
},
2
);
expect(mappedBuild.name).toContain("November 8th, 2022");
expect(datetimeStatus).toContain("November 8th, 2022");
});
});
Loading

0 comments on commit 30d7ffa

Please sign in to comment.