frontend/app/BranchesComponent.tsx (287 lines of code) (raw):
import React, { useEffect, useState } from "react";
import { RouteComponentProps, useHistory } from "react-router";
import {
Button,
Chip,
Grid,
LinearProgress,
makeStyles,
Typography,
} from "@material-ui/core";
import axios from "axios";
import {
SystemNotification,
SystemNotifcationKind,
} from "@guardian/pluto-headers";
import DeploymentStatusIcon from "./deploymentstatusicon";
import DockerImageName from "./dockerimagename";
import { ChevronLeft, ChevronRight } from "@material-ui/icons";
import MergeRequestInfoCell from "./MergeRequestInfoCell";
import BuildsInfoCell from "./buildsinfocell";
interface BranchesRouteParams {
deployment_name: string;
}
const useStyles = makeStyles((theme) => ({
banner: {
/*borderWidth: "2px",
borderColor: theme.palette.text.secondary,
borderRadius: "10px",*/
marginBottom: "2em",
},
deploymentLabel: {
marginRight: "1em",
marginTop: "1em",
},
branchInfo: {
marginTop: "2em",
marginLeft: "1em",
marginRight: "1em",
borderStyle: "solid",
borderRadius: "10px",
borderColor: theme.palette.text.secondary,
},
endOfList: {
width: "30vw",
marginLeft: "auto",
marginRight: "auto",
textAlign: "center",
marginTop: "8em",
fontStyle: "italic",
},
}));
const BranchesComponent: React.FC<RouteComponentProps<BranchesRouteParams>> = (
props
) => {
const [knownBranches, setKnownBranches] = useState<GitlabBranch[]>([]);
const [knownMergeRequests, setKnownMergeRequests] = useState<
GitlabMergeRequest[]
>([]);
const [totalBranchesCount, setTotalBranchesCount] = useState(0);
const [totalMRCount, setTotalMRCount] = useState(0);
const [displayBranchesLimit, setDisplayBranchesLimit] = useState(8);
const [currentDeployment, setCurrentDeployment] =
useState<DeployedImageInfo | undefined>(undefined);
const [loading, setLoading] = useState(true);
const classes = useStyles();
const history = useHistory();
const refreshBranches = async (project_id: string) => {
if (!project_id) {
SystemNotification.open(
SystemNotifcationKind.Error,
"This deployment has no project id registered, can't get branches"
);
return;
} else {
setLoading(true);
try {
const response = await axios.get<GitlabBranch[]>(
`/api/project/${project_id}/branches`
);
const branchesInMergeRequests = knownMergeRequests.map(
(mr) => mr.source_branch
);
const nonDuplicateBranches = response.data.filter(
(b) => !branchesInMergeRequests.includes(b.name)
);
setTotalBranchesCount(nonDuplicateBranches.length);
if (nonDuplicateBranches.length < displayBranchesLimit) {
setKnownBranches(nonDuplicateBranches);
} else {
setKnownBranches(nonDuplicateBranches.slice(0, displayBranchesLimit));
}
setLoading(false);
} catch (err) {
console.error(err);
SystemNotification.open(
SystemNotifcationKind.Error,
"Could not load branch information"
);
setLoading(false);
}
}
};
const refreshMergeRequests = async (project_id: string) => {
if (!project_id) {
SystemNotification.open(
SystemNotifcationKind.Error,
"This deployment has no project id registered, can't get merge requests"
);
return;
} else {
setLoading(true);
try {
const response = await axios.get<GitlabMergeRequest[]>(
`/api/project/${project_id}/mergerequests`
);
setTotalMRCount(response.data.length);
if (response.data.length < displayBranchesLimit) {
setKnownMergeRequests(response.data);
} else {
setKnownMergeRequests(response.data.slice(0, displayBranchesLimit));
}
setLoading(false);
} catch (err) {
console.error(err);
SystemNotification.open(
SystemNotifcationKind.Error,
"Could not load branch information"
);
setLoading(false);
}
}
};
const getGLProjectId = (currentDeployment: DeployedImageInfo) => {
try {
return parseInt(currentDeployment.labels["gitlab-project-id"]);
} catch (err) {
console.error("Could not get Gitlab project id: ", err);
return undefined;
}
};
const getGHProjectId = (currentDeployment: DeployedImageInfo) => {
try {
return currentDeployment.labels["github-project-name"];
} catch (err) {
console.error("Could not get Github project id: ", err);
return undefined;
}
};
const withProjectId = (cb: (project_id: string) => void) => {
if (currentDeployment) {
const maybeGLproject = getGLProjectId(currentDeployment);
const maybeProjectIdString = maybeGLproject
? maybeGLproject.toString()
: getGHProjectId(currentDeployment);
if (maybeProjectIdString) {
cb(maybeProjectIdString);
} else {
SystemNotification.open(
SystemNotifcationKind.Error,
"Could not find project id for this component"
);
}
}
};
useEffect(() => {
withProjectId(refreshBranches);
}, [knownMergeRequests]);
useEffect(() => {
withProjectId(refreshMergeRequests);
}, [currentDeployment, displayBranchesLimit]);
const refreshCurrentDeployment = async () => {
setLoading(true);
const name = props.match.params.deployment_name;
try {
const response = await axios.get<DeployedImageInfo>(
`/api/deployment/${name}`
);
setLoading(false);
setCurrentDeployment(response.data);
} catch (err) {
console.error(err);
SystemNotification.open(
SystemNotifcationKind.Error,
"Could not load current deployment information"
);
setLoading(false);
}
};
useEffect(() => {
refreshCurrentDeployment();
}, []);
return (
<>
<Grid
container
className={classes.banner}
direction="row"
spacing={2}
justify="center"
>
<Grid item style={{ alignSelf: "flex-end" }}>
<Button
variant="outlined"
startIcon={<ChevronLeft />}
onClick={() => history.goBack()}
>
Back
</Button>
</Grid>
<Grid item>
<Typography variant="h4">
{currentDeployment?.deploymentName ?? ""}
</Typography>
<Typography>
{currentDeployment?.readyReplicas} ready and{" "}
{currentDeployment?.notReadyReplicas} not ready
{currentDeployment?.readyReplicas &&
currentDeployment?.notReadyReplicas ? (
<DeploymentStatusIcon
availableReplicas={currentDeployment?.readyReplicas}
notAvailableReplicas={currentDeployment?.notReadyReplicas}
/>
) : null}
</Typography>
{currentDeployment
? Object.keys(currentDeployment.labels).map((labelName) => (
<Chip
key={labelName}
label={`${labelName}: ${currentDeployment.labels[labelName]}`}
className={classes.deploymentLabel}
/>
))
: null}
{loading ? (
<LinearProgress style={{ marginTop: "1em" }} />
) : undefined}
</Grid>
<Grid>
<ul>
{currentDeployment
? currentDeployment.deployedImages.map((imageInfo, idx) => (
<li key={idx}>
<DockerImageName image={imageInfo} />
</li>
))
: null}
</ul>
</Grid>
</Grid>
{/* merge request information */}
<Grid container justify="center" spacing={3}>
{currentDeployment
? knownMergeRequests.map((mr, idx) => (
<Grid item className={classes.branchInfo} key={idx} xs={3}>
<MergeRequestInfoCell
deploymentInfo={currentDeployment}
mr={mr}
/>
</Grid>
))
: undefined}
</Grid>
{/* branches information */}
<Grid container justify="center" spacing={3}>
{currentDeployment
? knownBranches.map((branch, idx) => (
<Grid item className={classes.branchInfo} key={idx} xs={3}>
<BuildsInfoCell
deploymentInfo={currentDeployment}
branchName={branch.name}
/>
</Grid>
))
: undefined}
</Grid>
{totalBranchesCount > displayBranchesLimit ? (
<div className={classes.endOfList}>
<Typography>
There are {totalBranchesCount - displayBranchesLimit} more branches
not shown here
</Typography>
<Button
onClick={() => setDisplayBranchesLimit((prev) => prev + 8)}
endIcon={<ChevronRight />}
>
Show more
</Button>
</div>
) : undefined}
</>
);
};
export default BranchesComponent;