import type { ReactView } from "@ethicdevs/react-monolith";
import React from "react";
import type {
Artefact,
Organization,
Pipeline,
Repository,
Stage,
User,
} from "@prisma/client";
import type { CommonProps, RepositoryWithForkedFromRepo } from "../../types";
import { AppRoute } from "../../routes.defs";
import { Card } from "../../components/Card.styled";
import { CountersRow } from "../../components/CountersRow";
import { Chip } from "../../components/Chip";
import { Grid } from "../../components/Grid";
import { Layout } from "../../components/Layout";
import { PageWrapper } from "../../components/PageWrapper";
import { buildRouteLink } from "../../utils/shared";
import { Colors, NamedColors } from "../../utils/style";
import { IslandWrapper } from "../../components/IslandWrapper.styled";
import RepositoryHero from "../../islands/RepositoryHero";
type PipelinesFilter =
| "all"
| "passed"
| "pending"
| "running"
| "failed"
| "canceled";
export interface PipelinesViewProps extends CommonProps {
parentOrg: Organization;
pipelines: (Pipeline & {
stages: Stage[];
triggeredByUser: Omit<User, "hashedPassword" | "email"> | null;
artefacts: Artefact[];
repo: Repository;
})[];
repo: RepositoryWithForkedFromRepo;
pipelinesFilter?: PipelinesFilter;
}
const PipelinesView: ReactView<PipelinesViewProps> = ({
commonProps,
parentOrg,
repo,
pipelines,
pipelinesFilter = "passed",
}) => {
return (
<Layout
{...commonProps}
showDrawerPrimary
orgSlug={parentOrg.slug}
repoSlug={repo.slug}
>
<PageWrapper>
<IslandWrapper
style={{ position: "sticky", top: 64, zIndex: 9998 }}
data-islandid={`${RepositoryHero.name}$$0`}
>
<RepositoryHero
themeScheme={commonProps.themeScheme}
forkedFromRepo={repo.forkedFromRepo}
forksCount={repo.forks.length}
parentOrg={parentOrg}
repo={repo}
path={`Pipelines`}
showForkButton={false}
showNewButton
newButtonText={"Trigger Pipeline"}
newButtonUrl={buildRouteLink(
AppRoute.REPOSITORY_PIPELINE_TRIGGER_ACTION,
{
orgSlug: parentOrg.slug,
repoSlug: repo.slug,
pipelineId: "",
},
)}
/>
</IslandWrapper>
<CountersRow
themeScheme={commonProps.themeScheme}
labels={[
{
label: "All",
counter: pipelines.length,
href:
buildRouteLink(AppRoute.REPOSITORY_PIPELINES, {
orgSlug: parentOrg.slug,
repoSlug: repo.slug,
}) + `?filter=all`,
active: pipelinesFilter == "all",
},
{
label: "Pending",
counter: commonProps.layoutCounters?.pipelinesPending || 0,
href:
buildRouteLink(AppRoute.REPOSITORY_PIPELINES, {
orgSlug: parentOrg.slug,
repoSlug: repo.slug,
}) + `?filter=pending`,
active: pipelinesFilter == "pending",
},
{
label: "Running",
counter: commonProps.layoutCounters?.pipelinesRunning || 0,
href:
buildRouteLink(AppRoute.REPOSITORY_PIPELINES, {
orgSlug: parentOrg.slug,
repoSlug: repo.slug,
}) + `?filter=running`,
active: pipelinesFilter == "running",
},
{
label: "Passed",
counter: commonProps.layoutCounters?.pipelinesPassed || 0,
href:
buildRouteLink(AppRoute.REPOSITORY_PIPELINES, {
orgSlug: parentOrg.slug,
repoSlug: repo.slug,
}) + `?filter=passed`,
active: pipelinesFilter == "passed",
},
{
label: "Failed",
counter: commonProps.layoutCounters?.pipelinesFailed || 0,
href:
buildRouteLink(AppRoute.REPOSITORY_PIPELINES, {
orgSlug: parentOrg.slug,
repoSlug: repo.slug,
}) + `?filter=failed`,
active: pipelinesFilter == "failed",
},
{
label: "Canceled",
counter: commonProps.layoutCounters?.pipelinesCanceled || 0,
href:
buildRouteLink(AppRoute.REPOSITORY_PIPELINES, {
orgSlug: parentOrg.slug,
repoSlug: repo.slug,
}) + `?filter=canceled`,
active: pipelinesFilter == "canceled",
},
]}
/>
<Grid.Col fluid gap={4} style={{ marginTop: 12 }}>
{pipelines != null && pipelines.length >= 1 ? (
pipelines.map((pipeline) => (
<Card
key={pipeline.id}
themeScheme={commonProps.themeScheme}
style={{ width: "100%", padding: 8 }}
>
<Grid.Col fluid>
<a
style={{ width: "100%" }}
href={buildRouteLink(AppRoute.REPOSITORY_PIPELINE_DETAILS, {
orgSlug: parentOrg.slug,
repoSlug: repo.slug,
pipelineId: pipeline.id,
})}
>
<Grid.Row fluid alignItems={"center"} gap={4}>
<span>{pipeline.name}</span>
<span>•</span>
<span
style={{
flex: 1,
color:
NamedColors.TEXT_MUTED[commonProps.themeScheme],
}}
>
{pipeline.id}
</span>
<Chip
themeScheme={commonProps.themeScheme}
color={
{
PENDING: Colors.PRIMARY_01,
RUNNING: Colors.CYAN_01,
PASSED: Colors.GREEN_01,
FAILED: Colors.RED_01,
CANCELED: Colors.BLACK_01,
}[pipeline.status]
}
>
{
{
PENDING: "Pending",
RUNNING: "Running",
PASSED: "Passed",
FAILED: "Failed",
CANCELED: "Canceled",
}[pipeline.status]
}
</Chip>
</Grid.Row>
</a>
{pipeline.triggeredByUser != null && (
<Grid.Row
fluid
nowrap
gap={8}
alignItems={"center"}
style={{ opacity: 0.67 }}
>
triggered by
<a
href={buildRouteLink(
AppRoute.USER_DETAILS,
{ username: `${pipeline.triggeredByUser.username}` },
{ encodeURIComponent: false },
)}
style={{ textTransform: "none" }}
>
@{pipeline.triggeredByUser.username}
</a>
</Grid.Row>
)}
<Grid.Col
fluid
gap={4}
style={{ opacity: 0.67, marginTop: 4 }}
>
{new Date(pipeline.createdAt).getTime() <=
new Date(pipeline.updatedAt).getTime() && (
<span>
{`opened on ${new Date(pipeline.createdAt).toLocaleString()}`}
</span>
)}
{pipeline.closedAt != null && (
<span>
{`closed on ${new Date(pipeline.closedAt).toLocaleString()}`}
</span>
)}
</Grid.Col>
</Grid.Col>
</Card>
))
) : (
<Grid.Col
fluid
nowrap
justifyContent={"center"}
alignItems={"center"}
style={{ minHeight: "70vh" }}
>
<h1 style={{ margin: 0 }}>No Pipeline Yet</h1>
<p
style={{
maxWidth: "62%",
textAlign: "center",
lineHeight: 1.8,
}}
>
<span>Be the change you want to see, </span>
<a
href={buildRouteLink(
AppRoute.REPOSITORY_PIPELINE_TRIGGER_ACTION,
{
orgSlug: parentOrg.slug,
repoSlug: repo.slug,
pipelineId: "",
},
)}
>
trigger the first Pipeline run
</a>
<span> to this repository 🚀.</span>
</p>
</Grid.Col>
)}
</Grid.Col>
</PageWrapper>
</Layout>
);
};
PipelinesView.displayName = "PipelinesView";
export default PipelinesView;