feat(pipelines): show counters in layout drawers + page
+ 128
- 24
@@ -1,5 +1,5 @@
 {
-  "_generatedAtUnix": 1779047887440,
+  "_generatedAtUnix": 1779126421985,
   "_hashAlgorithm": "sha1",
   "_version": 2,
   "assets": {

...
@@ -40,7 +40,7 @@
       "pathSourceMap": "./public/.islands/PullRequestSourceSelect.bundle.js.map"
     },
     "RepositoriesList": {
-      "hash": "42a9c5e680868fed97e448c96287f3fa70935911",
+      "hash": "e701f14a291768e85303e656e1cb74b0ea430bb0",
       "pathSource": "./app/islands/RepositoriesList.tsx",
       "pathBundle": "./public/.islands/RepositoriesList.bundle.js",
       "pathSourceMap": "./public/.islands/RepositoriesList.bundle.js.map"

...
@@ -94,7 +94,7 @@
       "pathSourceMap": "./public/.islands/RepositoryTreeView.bundle.js.map"
     },
     "SSHKeyItem": {
-      "hash": "38e32ae581341246ab4379f701dcba9a74d7cf71",
+      "hash": "215756520e63cc93d9c8be96c11a75feb84401b6",
       "pathSource": "./app/islands/SSHKeyItem.tsx",
       "pathBundle": "./public/.islands/SSHKeyItem.bundle.js",
       "pathSourceMap": "./public/.islands/SSHKeyItem.bundle.js.map"

...
@@ -138,7 +138,7 @@
       "pathSource": "./app/views/pipelines/PipelineStagesView.tsx"
     },
     "PipelinesView": {
-      "hash": "01cd834642b220a0047844a5fdd724d0dbf1fa37",
+      "hash": "b0b0751c7a869918310db58db9441ac2380fea97",
       "pathSource": "./app/views/pipelines/PipelinesView.tsx"
     },
     "RepositoryBrowserView": {

app/components/DrawerPrimary.tsx
@@ -239,7 +239,9 @@ export const DrawerPrimary = ({
               }
             >
               <span>Pipelines</span>
-              <Chip themeScheme={themeScheme}>{counters.builds || 0}</Chip>
+              <Chip themeScheme={themeScheme}>
+                {counters.pipelinesTotal || 0}
+              </Chip>
             </StyledDrawerListItem>
             <StyledDrawerListItem themeScheme={themeScheme} disabled>
               <span>Tests & Coverage</span>

app/components/MarkdownToJsx.tsx
@@ -118,6 +118,7 @@ export const MarkdownToJsx: VFC<MarkdownToJsxProps> = ({
                     : JSON.parse(lightSrc)
                 }
                 style={{
+                  maxWidth: "100%",
                   width: "100%",
                   height: "auto",
                   minHeight: `${minHeight}px`,

app/islands/SSHKeyItem.tsx
@@ -6,6 +6,7 @@ import { UserSSHKey } from "@prisma/client";
 // app
 import type { WithThemeSchemeProp } from "../types";
 import { Card } from "../components/Card.styled";
+import { Chip } from "../components/Chip";
 import { Grid } from "../components/Grid";
 import { KeyIcon } from "../components/icons/KeyIcon";
 import { NamedColors } from "../utils/style";

...
@@ -17,7 +18,7 @@ interface SSHKeyItemProps {
 }
 
 const SSHKeyItem: ReactIsland<SSHKeyItemProps & WithThemeSchemeProp> = ({
-  sshKey: { id, createdAt, name, key },
+  sshKey: { id, createdAt, name, key, key_fingerprint },
   themeScheme,
 }) => {
   const [show, setShow] = useState<boolean>(false);

...
@@ -31,10 +32,16 @@ const SSHKeyItem: ReactIsland<SSHKeyItemProps & WithThemeSchemeProp> = ({
           alignItems="center"
           onClick={() => setShow((prev) => !prev)}
         >
-          <Grid.Row fluid nowrap gap={8} alignItems={"center"}>
-            <KeyIcon color={NamedColors.TEXT_DEFAULT[themeScheme]} size={24} />
-            <div>{name}</div>
-          </Grid.Row>
+          <Grid.Col nowrap gap={4}>
+            <Grid.Row fluid nowrap gap={8} alignItems={"center"}>
+              <KeyIcon
+                color={NamedColors.TEXT_DEFAULT[themeScheme]}
+                size={24}
+              />
+              <div>{name}</div>
+            </Grid.Row>
+            <Chip themeScheme={themeScheme}>{key_fingerprint}</Chip>
+          </Grid.Col>
           <Grid.Col nowrap gap={4}>
             <div style={{ whiteSpace: "nowrap" }}>Added on:</div>
             <div style={{ whiteSpace: "nowrap" }}>

app/services/repository/getRepositoryCounters.ts
@@ -1,6 +1,7 @@
 // 1st-party
 import { InMemoryCacheAdapter } from "@ethicdevs/fastify-stream-react-views";
 import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
+import { PipelineStatus } from "@prisma/client";
 // app
 import { RepositoryCountersDTO } from "../../types";
 // service

...
@@ -80,6 +81,71 @@ const makeGetRepositoryCounters: ServiceMethodFactory<
           })
         : 0;
 
+    const pendingPipelines =
+      orgSlug != null && repoSlug != null
+        ? await request.prisma.pipeline.count({
+            where: {
+              repo: {
+                organization: { slug: orgSlug },
+                slug: repoSlug,
+              },
+              status: PipelineStatus.PENDING,
+            },
+          })
+        : 0;
+
+    const runningPipelines =
+      orgSlug != null && repoSlug != null
+        ? await request.prisma.pipeline.count({
+            where: {
+              repo: {
+                organization: { slug: orgSlug },
+                slug: repoSlug,
+              },
+              status: PipelineStatus.RUNNING,
+            },
+          })
+        : 0;
+
+    const passedPipelines =
+      orgSlug != null && repoSlug != null
+        ? await request.prisma.pipeline.count({
+            where: {
+              repo: {
+                organization: { slug: orgSlug },
+                slug: repoSlug,
+              },
+              status: PipelineStatus.PASSED,
+            },
+          })
+        : 0;
+
+    const failedPipelines =
+      orgSlug != null && repoSlug != null
+        ? await request.prisma.pipeline.count({
+            where: {
+              repo: {
+                organization: { slug: orgSlug },
+                slug: repoSlug,
+              },
+              status: PipelineStatus.FAILED,
+            },
+          })
+        : 0;
+
+    const canceledPipelines =
+      orgSlug != null && repoSlug != null
+        ? await request.prisma.pipeline.count({
+            where: {
+              repo: {
+                organization: { slug: orgSlug },
+                slug: repoSlug,
+              },
+              status: PipelineStatus.CANCELED,
+            },
+          })
+        : 0;
+
     console.log("username:", username);
 
     // retrieve current user ssh keyrs count

...
@@ -135,6 +201,8 @@ const makeGetRepositoryCounters: ServiceMethodFactory<
     const counters: RepositoryCountersDTO = {
       files: 0,
       forks: forks,
+      likes: 0,
+      watchers: 0,
       branches: 0,
       tags: 0,
       commits: 0,

...
@@ -143,6 +211,23 @@ const makeGetRepositoryCounters: ServiceMethodFactory<
       pullsMerged: mergedPulls,
       pullsClosed: closedPulls,
       pullsTotal: openPulls + mergedPulls + closedPulls,
+      pipelines:
+        pendingPipelines +
+        runningPipelines +
+        passedPipelines +
+        failedPipelines +
+        canceledPipelines,
+      pipelinesPending: pendingPipelines,
+      pipelinesRunning: runningPipelines,
+      pipelinesPassed: passedPipelines,
+      pipelinesFailed: failedPipelines,
+      pipelinesCanceled: canceledPipelines,
+      pipelinesTotal:
+        pendingPipelines +
+        runningPipelines +
+        passedPipelines +
+        failedPipelines +
+        canceledPipelines,
       tests: 0,
       builds: 0,
       issues: 0,

...
@@ -174,6 +259,13 @@ const DEFAULT_COUNTERS: RepositoryCountersDTO = {
   pullsMerged: 0,
   pullsClosed: 0,
   pullsTotal: 0,
+  pipelines: 0,
+  pipelinesPending: 0,
+  pipelinesRunning: 0,
+  pipelinesPassed: 0,
+  pipelinesFailed: 0,
+  pipelinesCanceled: 0,
+  pipelinesTotal: 0,
   tests: 0,
   builds: 0,
   issues: 0,

app/services/user/addUserSSHKey.ts
@@ -42,20 +42,15 @@ const addUserSSHKey: ServiceMethodFactory<
     const fingerprintSHA256Process = spawn("ssh-keygen", ["-l", "-f", "-"], {
       env: { LANG: "C" },
     });
-
-    // pipe key to ssh-keygen stdin
-    fingerprintSHA256Process.stdin.write(key);
+    fingerprintSHA256Process.stdin.write(key); // pipe key to ssh-keygen stdin
+    const fingerprintSHA256 = await spawnConcatChunks(fingerprintSHA256Process);
 
     const fingerprintMD5Process = spawn(
       "ssh-keygen",
       ["-lf", "-E", "md5", "-"],
       { env: { LANG: "C" } },
     );
-
-    // pipe key to ssh-keygen stdin
-    fingerprintMD5Process.stdin.write(key);
-
-    const fingerprintSHA256 = await spawnConcatChunks(fingerprintSHA256Process);
+    fingerprintMD5Process.stdin.write(key); // pipe key to ssh-keygen stdin
     const fingerprintMD5 = await spawnConcatChunks(fingerprintMD5Process);
 
     console.log("fingerprints:", {

...
@@ -85,7 +80,7 @@ const addUserSSHKey: ServiceMethodFactory<
     }
 
     let line = "";
-    line += `environment="KEY=${key},KEY_FINGERPRINT=${fingerprint}",`;
+    line += `environment="KEY=${key},KEY_FINGERPRINT=${fingerprintSHA256}",`;
     line += `command="ssh_command ${user.username}",`;
     line += "no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ";
     line += `${key}\n`;

@@ -220,6 +220,13 @@ export interface RepositoryCountersDTO {
   pullsMerged?: number;
   pullsClosed?: number;
   pullsTotal?: number;
+  pipelines?: number;
+  pipelinesPending?: number;
+  pipelinesRunning?: number;
+  pipelinesPassed?: number;
+  pipelinesFailed?: number;
+  pipelinesCanceled?: number;
+  pipelinesTotal?: number;
   tests?: number;
   builds?: number;
   issues?: number;

app/views/pipelines/PipelinesView.tsx
@@ -95,7 +95,7 @@ const PipelinesView: ReactView<PipelinesViewProps> = ({
             },
             {
               label: "Pending",
-              counter: 0,
+              counter: commonProps.layoutCounters?.pipelinesPending || 0,
               href:
                 buildRouteLink(AppRoute.REPOSITORY_PIPELINES, {
                   orgSlug: parentOrg.slug,

...
@@ -105,7 +105,7 @@ const PipelinesView: ReactView<PipelinesViewProps> = ({
             },
             {
               label: "Running",
-              counter: 0,
+              counter: commonProps.layoutCounters?.pipelinesRunning || 0,
               href:
                 buildRouteLink(AppRoute.REPOSITORY_PIPELINES, {
                   orgSlug: parentOrg.slug,

...
@@ -115,7 +115,7 @@ const PipelinesView: ReactView<PipelinesViewProps> = ({
             },
             {
               label: "Passed",
-              counter: 0,
+              counter: commonProps.layoutCounters?.pipelinesPassed || 0,
               href:
                 buildRouteLink(AppRoute.REPOSITORY_PIPELINES, {
                   orgSlug: parentOrg.slug,

...
@@ -125,7 +125,7 @@ const PipelinesView: ReactView<PipelinesViewProps> = ({
             },
             {
               label: "Failed",
-              counter: 0,
+              counter: commonProps.layoutCounters?.pipelinesFailed || 0,
               href:
                 buildRouteLink(AppRoute.REPOSITORY_PIPELINES, {
                   orgSlug: parentOrg.slug,

...
@@ -135,7 +135,7 @@ const PipelinesView: ReactView<PipelinesViewProps> = ({
             },
             {
               label: "Canceled",
-              counter: 0,
+              counter: commonProps.layoutCounters?.pipelinesCanceled || 0,
               href:
                 buildRouteLink(AppRoute.REPOSITORY_PIPELINES, {
                   orgSlug: parentOrg.slug,