.ts
TypeScript
(application/typescript)
// 1st-party
import type { ReactView } from "@ethicdevs/react-monolith";
// 3rd-party
import React from "react";
// generated via script[generate:prisma]
import type {
  Artefact,
  Organization,
  Pipeline,
  Repository,
  Stage,
  User,
} from "@prisma/client";
// app
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";
// app islands
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: 23000 }}
          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"}
            // newButtonText={
            //   <>
            //     <span>New Pull Request</span>
            //     <GitPullIcon
            //       color={Colors.WHITE_01}
            //       size={24}
            //     />
            //   </>
            // }
            newButtonUrl={buildRouteLink(
              AppRoute.REPOSITORY_PIPELINE_TRIGGER_ACTION,
              {
                orgSlug: parentOrg.slug,
                repoSlug: repo.slug,
                pipelineId: "", // @fixme: ???
              },
            )}
          />
        </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>&bull;</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: "", // @fixme: ???
                    },
                  )}
                >
                  trigger the first Pipeline run
                </a>
                <span> to this repository 🚀.</span>
              </p>
            </Grid.Col>
          )}
        </Grid.Col>
      </PageWrapper>
    </Layout>
  );
};

PipelinesView.displayName = "PipelinesView";
export default PipelinesView;